mirror of
https://github.com/remnawave/panel.git
synced 2026-04-26 17:56:25 +00:00
feat: add ClientCard and ClientsList components to MDXComponents
This commit is contained in:
210
src/components/ClientCard/index.tsx
Normal file
210
src/components/ClientCard/index.tsx
Normal file
@@ -0,0 +1,210 @@
|
||||
import GitHubStars from '@site/src/components/GitHubStars'
|
||||
import Link from '@docusaurus/Link'
|
||||
import React from 'react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import styles from './styles.module.css'
|
||||
|
||||
type CoreType = 'mihomo' | 'other' | 'singbox' | 'xray'
|
||||
|
||||
interface ClientCardProps {
|
||||
author?: string
|
||||
authorLink?: string
|
||||
core: CoreType
|
||||
coreIcon?: string // SVG icon URL
|
||||
description: string
|
||||
featured?: boolean
|
||||
githubRepo?: string
|
||||
hwidSupported?: boolean
|
||||
id?: string
|
||||
links?: {
|
||||
docs?: string
|
||||
download?: string
|
||||
github?: string
|
||||
telegram?: string
|
||||
website?: string
|
||||
}
|
||||
platform?: string
|
||||
title: string
|
||||
}
|
||||
|
||||
const CORE_CONFIG: Record<
|
||||
CoreType,
|
||||
{ color: string; darkColor: string; iconSvg?: string; label: string }
|
||||
> = {
|
||||
mihomo: {
|
||||
label: 'Mihomo',
|
||||
iconSvg: '/clients/mihomo_black.svg',
|
||||
color: '#3a4f66',
|
||||
darkColor: '#2a3847'
|
||||
},
|
||||
xray: {
|
||||
label: 'X-Ray',
|
||||
iconSvg: '/clients/xray_black.svg',
|
||||
color: '#4a3d5a',
|
||||
darkColor: '#3a2d4a'
|
||||
},
|
||||
singbox: {
|
||||
label: 'Sing-Box',
|
||||
iconSvg: '/clients/sb_black.svg',
|
||||
color: '#2d4a3d',
|
||||
darkColor: '#1d3a2d'
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: '#3d4349',
|
||||
darkColor: '#2d3339'
|
||||
}
|
||||
}
|
||||
|
||||
export default function ClientCard({
|
||||
title,
|
||||
description,
|
||||
author,
|
||||
authorLink,
|
||||
links,
|
||||
core,
|
||||
coreIcon,
|
||||
featured = false,
|
||||
hwidSupported = false,
|
||||
githubRepo,
|
||||
id,
|
||||
platform
|
||||
}: ClientCardProps) {
|
||||
const clientId =
|
||||
id ||
|
||||
title
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
|
||||
const coreConfig = CORE_CONFIG[core] || CORE_CONFIG.other
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(styles.clientCard, featured && styles.featured)}
|
||||
data-core={core}
|
||||
id={clientId}
|
||||
>
|
||||
{/* Content */}
|
||||
<div className={styles.cardContent}>
|
||||
{/* Badges Row - Always render to maintain consistent spacing */}
|
||||
<div className={styles.badgesRow}>
|
||||
{featured && <span className={styles.featuredBadge}>⭐</span>}
|
||||
{hwidSupported && (
|
||||
<div
|
||||
className={styles.hwidBadge}
|
||||
data-tooltip="This client supports sending HWID"
|
||||
>
|
||||
<svg fill="currentColor" height="14" viewBox="0 0 16 16" width="14">
|
||||
<path d="M13.485 1.431a1.473 1.473 0 0 1 2.104 2.062l-7.84 9.801a1.473 1.473 0 0 1-2.12.04L.431 8.138a1.473 1.473 0 0 1 2.084-2.083l4.111 4.112 6.82-8.69a.486.486 0 0 1 .04-.045z" />
|
||||
</svg>
|
||||
<span>HWID</span>
|
||||
</div>
|
||||
)}
|
||||
{githubRepo && links?.github && (
|
||||
<Link className={styles.starsBadge} to={links.github}>
|
||||
<GitHubStars repo={githubRepo} />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.cardHeader}>
|
||||
<div className={styles.titleWrapper}>
|
||||
<h3 className={styles.clientTitle}>
|
||||
<a className={styles.clientAnchor} href={`#${clientId}`}>
|
||||
{title}
|
||||
</a>
|
||||
</h3>
|
||||
{author && (
|
||||
<div className={styles.authorInfo}>
|
||||
<span className={styles.byText}>by</span>
|
||||
{authorLink ? (
|
||||
<Link className={styles.authorLink} to={authorLink}>
|
||||
{author}
|
||||
</Link>
|
||||
) : (
|
||||
<span className={styles.authorName}>{author}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{platform && <div className={styles.platformBadge}>{platform}</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className={styles.description}>{description}</p>
|
||||
|
||||
{links && (
|
||||
<div className={styles.links}>
|
||||
{links?.download && (
|
||||
<Link
|
||||
className={clsx(styles.link, styles.downloadLink)}
|
||||
to={links.download}
|
||||
>
|
||||
<svg fill="currentColor" height="14" viewBox="0 0 16 16" width="14">
|
||||
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
|
||||
<path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z" />
|
||||
</svg>
|
||||
Download
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{links?.github && (
|
||||
<Link
|
||||
className={clsx(styles.link, styles.githubLink)}
|
||||
to={links.github}
|
||||
>
|
||||
<svg fill="currentColor" height="14" viewBox="0 0 16 16" width="14">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
|
||||
</svg>
|
||||
GitHub
|
||||
</Link>
|
||||
)}
|
||||
{links?.docs && (
|
||||
<Link className={clsx(styles.link, styles.docsLink)} to={links.docs}>
|
||||
<svg fill="currentColor" height="14" viewBox="0 0 16 16" width="14">
|
||||
<path d="M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v13.5a.5.5 0 0 1-.777.416L8 13.101l-5.223 2.815A.5.5 0 0 1 2 15.5V2zm2-1a1 1 0 0 0-1 1v12.566l4.723-2.482a.5.5 0 0 1 .554 0L13 14.566V2a1 1 0 0 0-1-1H4z" />
|
||||
</svg>
|
||||
Docs
|
||||
</Link>
|
||||
)}
|
||||
{links?.telegram && (
|
||||
<Link
|
||||
className={clsx(styles.link, styles.telegramLink)}
|
||||
to={links.telegram}
|
||||
>
|
||||
<svg fill="currentColor" height="14" viewBox="0 0 16 16" width="14">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8.287 5.906c-.778.324-2.334.994-4.666 2.01-.378.15-.577.298-.595.442-.03.243.275.339.69.47l.175.055c.408.133.958.288 1.243.294.26.006.549-.1.868-.32 2.179-1.471 3.304-2.214 3.374-2.23.05-.012.12-.026.166.016.047.041.042.12.037.141-.03.129-1.227 1.241-1.846 1.817-.193.18-.33.307-.358.336a8.154 8.154 0 0 1-.188.186c-.38.366-.664.64.015 1.088.327.216.589.393.85.571.284.194.568.387.936.629.093.06.183.125.27.187.331.236.63.448.997.414.214-.02.435-.22.547-.82.265-1.417.786-4.486.906-5.751a1.426 1.426 0 0 0-.013-.315.337.337 0 0 0-.114-.217.526.526 0 0 0-.31-.093c-.3.005-.763.166-2.984 1.09z" />
|
||||
</svg>
|
||||
Telegram
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{links?.website && (
|
||||
<Link
|
||||
className={clsx(styles.link, styles.websiteLink)}
|
||||
to={links.website}
|
||||
>
|
||||
<svg fill="currentColor" height="14" viewBox="0 0 16 16" width="14">
|
||||
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855A7.97 7.97 0 0 0 5.145 4H7.5V1.077zM4.09 4a9.267 9.267 0 0 1 .64-1.539 6.7 6.7 0 0 1 .597-.933A7.025 7.025 0 0 0 2.255 4H4.09zm-.582 3.5c.03-.877.138-1.718.312-2.5H1.674a6.958 6.958 0 0 0-.656 2.5h2.49zM4.847 5a12.5 12.5 0 0 0-.338 2.5H7.5V5H4.847zM8.5 5v2.5h2.99a12.495 12.495 0 0 0-.337-2.5H8.5zM4.51 8.5a12.5 12.5 0 0 0 .337 2.5H7.5V8.5H4.51zm3.99 0V11h2.653c.187-.765.306-1.608.338-2.5H8.5zM5.145 12c.138.386.295.744.468 1.068.552 1.035 1.218 1.65 1.887 1.855V12H5.145zm.182 2.472a6.696 6.696 0 0 1-.597-.933A9.268 9.268 0 0 1 4.09 12H2.255a7.024 7.024 0 0 0 3.072 2.472zM3.82 11a13.652 13.652 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5H3.82zm6.853 3.472A7.024 7.024 0 0 0 13.745 12H11.91a9.27 9.27 0 0 1-.64 1.539 6.688 6.688 0 0 1-.597.933zM8.5 12v2.923c.67-.204 1.335-.82 1.887-1.855.173-.324.33-.682.468-1.068H8.5zm3.68-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.65 13.65 0 0 1-.312 2.5zm2.802-3.5a6.959 6.959 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5h2.49zM11.27 2.461c.247.464.462.98.64 1.539h1.835a7.024 7.024 0 0 0-3.072-2.472c.218.284.418.598.597.933zM10.855 4a7.966 7.966 0 0 0-.468-1.068C9.835 1.897 9.17 1.282 8.5 1.077V4h2.355z" />
|
||||
</svg>
|
||||
Website
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Core Badge - Bottom */}
|
||||
<div className={styles.coreBadge} data-core={core}>
|
||||
{(coreIcon || coreConfig.iconSvg) && (
|
||||
<img
|
||||
alt={coreConfig.label}
|
||||
className={styles.coreIconSvg}
|
||||
src={coreIcon || coreConfig.iconSvg}
|
||||
/>
|
||||
)}
|
||||
<span className={styles.coreLabel}>{coreConfig.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
557
src/components/ClientCard/styles.module.css
Normal file
557
src/components/ClientCard/styles.module.css
Normal file
@@ -0,0 +1,557 @@
|
||||
.clientCard {
|
||||
background: var(--ifm-card-background-color);
|
||||
border: 1px solid var(--ifm-color-emphasis-200);
|
||||
border-radius: 12px;
|
||||
overflow: visible;
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
scroll-margin-top: 100px;
|
||||
}
|
||||
|
||||
.clientCard:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .clientCard:hover {
|
||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* Core-specific border colors on hover */
|
||||
.clientCard[data-core='mihomo']:hover {
|
||||
border-color: #3a4f66;
|
||||
}
|
||||
|
||||
.clientCard[data-core='xray']:hover {
|
||||
border-color: #4a3d5a;
|
||||
}
|
||||
|
||||
.clientCard[data-core='singbox']:hover {
|
||||
border-color: #2d4a3d;
|
||||
}
|
||||
|
||||
.clientCard[data-core='other']:hover {
|
||||
border-color: #3d4349;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .clientCard[data-core='mihomo']:hover {
|
||||
border-color: #2a3847;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .clientCard[data-core='xray']:hover {
|
||||
border-color: #3a2d4a;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .clientCard[data-core='singbox']:hover {
|
||||
border-color: #1d3a2d;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .clientCard[data-core='other']:hover {
|
||||
border-color: #2d3339;
|
||||
}
|
||||
|
||||
.featured {
|
||||
border: 2px solid var(--ifm-color-primary);
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--ifm-card-background-color) 0%,
|
||||
rgba(var(--ifm-color-primary-rgb), 0.03) 100%
|
||||
);
|
||||
}
|
||||
|
||||
/* Core Badge - Bottom of Card */
|
||||
.coreBadge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.6rem 1rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.6px;
|
||||
margin-top: auto;
|
||||
border-radius: 0 0 12px 12px;
|
||||
}
|
||||
|
||||
.coreBadge[data-core='mihomo'] {
|
||||
background: #3a4f66;
|
||||
color: #d4e3f0;
|
||||
}
|
||||
|
||||
.coreBadge[data-core='xray'] {
|
||||
background: #4a3d5a;
|
||||
color: #e5dff0;
|
||||
}
|
||||
|
||||
.coreBadge[data-core='singbox'] {
|
||||
background: #2d4a3d;
|
||||
color: #d4f0e0;
|
||||
}
|
||||
|
||||
.coreBadge[data-core='other'] {
|
||||
background: #3d4349;
|
||||
color: #e0e3e6;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .coreBadge[data-core='mihomo'] {
|
||||
background: #2a3847;
|
||||
color: #b8d0e5;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .coreBadge[data-core='xray'] {
|
||||
background: #3a2d4a;
|
||||
color: #d5c8e5;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .coreBadge[data-core='singbox'] {
|
||||
background: #1d3a2d;
|
||||
color: #b8e5d0;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .coreBadge[data-core='other'] {
|
||||
background: #2d3339;
|
||||
color: #c8ced3;
|
||||
}
|
||||
|
||||
.coreIcon {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.coreIconSvg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
object-fit: contain;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.coreLabel {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
/* Badges Row */
|
||||
.badgesRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
min-height: 28px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.hwidBadge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
padding: 0.35rem 0.65rem;
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
border-radius: 6px;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
cursor: help;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hwidBadge::before {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
bottom: calc(100% + 10px);
|
||||
right: 0;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: var(--ifm-color-emphasis-900);
|
||||
color: white;
|
||||
border-radius: 6px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
opacity 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
transform: translateY(5px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.hwidBadge::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: calc(100% + 2px);
|
||||
right: 15px;
|
||||
border: 5px solid transparent;
|
||||
border-top-color: var(--ifm-color-emphasis-900);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s ease;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.hwidBadge:hover::before,
|
||||
.hwidBadge:hover::after {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.hwidBadge:hover {
|
||||
background: #c3e6cb;
|
||||
border-color: #b1dfbb;
|
||||
transform: translateY(-1px);
|
||||
z-index: 10001;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .hwidBadge {
|
||||
background: #1e4620;
|
||||
color: #a3d9a5;
|
||||
border-color: #2d5a2f;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .hwidBadge:hover {
|
||||
background: #2d5a2f;
|
||||
border-color: #3d6a3f;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .hwidBadge::before {
|
||||
background: var(--ifm-color-emphasis-100);
|
||||
color: var(--ifm-color-emphasis-900);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .hwidBadge::after {
|
||||
border-top-color: var(--ifm-color-emphasis-100);
|
||||
}
|
||||
|
||||
.starsBadge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.4rem 0.75rem;
|
||||
background: #24292e;
|
||||
color: white;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 6px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.starsBadge:hover {
|
||||
background: #1a1e22;
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .starsBadge {
|
||||
background: #2d333b;
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .starsBadge:hover {
|
||||
background: #373e47;
|
||||
border-color: rgba(255, 255, 255, 0.25);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Card Content */
|
||||
.cardContent {
|
||||
padding: 1.25rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.85rem;
|
||||
border-radius: 12px 12px 0 0;
|
||||
}
|
||||
|
||||
.featured .cardContent {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--ifm-card-background-color) 0%,
|
||||
rgba(var(--ifm-color-primary-rgb), 0.03) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.cardHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.titleWrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.clientTitle {
|
||||
margin: 0;
|
||||
font-size: 1.15rem;
|
||||
font-weight: 700;
|
||||
color: var(--ifm-heading-color);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.clientAnchor {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.clientAnchor:hover {
|
||||
color: var(--ifm-color-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.featuredBadge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.3rem;
|
||||
padding: 0.35rem 0.65rem;
|
||||
background: var(--ifm-color-primary);
|
||||
color: white;
|
||||
border-radius: 6px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 2px 8px rgba(var(--ifm-color-primary-rgb), 0.3);
|
||||
}
|
||||
|
||||
.authorInfo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.byText {
|
||||
color: var(--ifm-color-emphasis-600);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.authorLink {
|
||||
color: var(--ifm-color-primary);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.authorLink:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--ifm-color-primary-dark);
|
||||
}
|
||||
|
||||
.authorName {
|
||||
color: var(--ifm-color-emphasis-800);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .byText {
|
||||
color: var(--ifm-color-emphasis-500);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .authorName {
|
||||
color: var(--ifm-color-emphasis-700);
|
||||
}
|
||||
|
||||
.platformBadge {
|
||||
display: inline-block;
|
||||
padding: 0.2rem 0.5rem;
|
||||
background: var(--ifm-color-emphasis-100);
|
||||
color: var(--ifm-color-emphasis-700);
|
||||
border-radius: 4px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.description {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
color: var(--ifm-color-emphasis-800);
|
||||
line-height: 1.5;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
margin-top: auto;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid var(--ifm-color-emphasis-200);
|
||||
}
|
||||
|
||||
.link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.5rem 0.9rem;
|
||||
min-height: 36px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.githubLink {
|
||||
background: #24292e;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.githubLink:hover {
|
||||
background: #1a1e22;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 2px 8px rgba(36, 41, 46, 0.3);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .githubLink {
|
||||
background: #2d333b;
|
||||
color: white;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .githubLink:hover {
|
||||
background: #404852;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.docsLink {
|
||||
background: var(--ifm-color-primary-lightest);
|
||||
color: var(--ifm-color-primary-darker);
|
||||
}
|
||||
|
||||
.docsLink:hover {
|
||||
background: var(--ifm-color-primary);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.telegramLink {
|
||||
background: #0088cc;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.telegramLink:hover {
|
||||
background: #006699;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .telegramLink:hover {
|
||||
background: #33adff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.websiteLink {
|
||||
background: #0097a7;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.websiteLink:hover {
|
||||
background: #00838f;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .websiteLink {
|
||||
background: #00acc1;
|
||||
color: white;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .websiteLink:hover {
|
||||
background: #00bcd4;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.downloadLink {
|
||||
background: #475569;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.downloadLink:hover {
|
||||
background: #334155;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 2px 8px rgba(71, 85, 105, 0.3);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .downloadLink {
|
||||
background: #64748b;
|
||||
color: white;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .downloadLink:hover {
|
||||
background: #475569;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Mobile Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.coreBadge {
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.7rem;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.coreIcon {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.coreIconSvg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.coreLabel {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.badgesRow {
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.hwidBadge,
|
||||
.starsBadge,
|
||||
.featuredBadge {
|
||||
padding: 0.3rem 0.5rem;
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
.cardContent {
|
||||
padding: 1rem;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.clientTitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.link {
|
||||
padding: 0.45rem 0.75rem;
|
||||
min-height: 32px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
43
src/components/ClientsList/index.tsx
Normal file
43
src/components/ClientsList/index.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from 'react'
|
||||
|
||||
import ClientCard from '@site/src/components/ClientCard'
|
||||
import type { Client, Platform } from '@site/src/data/clients'
|
||||
import { getClientsByPlatform } from '@site/src/data/clients'
|
||||
|
||||
interface ClientsListProps {
|
||||
platform: Platform
|
||||
}
|
||||
|
||||
export default function ClientsList({ platform }: ClientsListProps) {
|
||||
const clients = getClientsByPlatform(platform)
|
||||
|
||||
return (
|
||||
<>
|
||||
{clients.map((client: Client) => {
|
||||
// Merge platform-specific download link with general links
|
||||
const platformDownloadLink = client.downloadLinks?.[platform]
|
||||
const links = {
|
||||
...client.links,
|
||||
...(platformDownloadLink && { download: platformDownloadLink })
|
||||
}
|
||||
|
||||
return (
|
||||
<ClientCard
|
||||
key={`${client.id}-${platform}`}
|
||||
id={`${client.id}-${platform}`}
|
||||
title={client.name}
|
||||
description={client.description}
|
||||
core={client.core}
|
||||
coreIcon={client.coreIcon}
|
||||
author={client.author}
|
||||
authorLink={client.authorLink}
|
||||
githubRepo={client.githubRepo}
|
||||
featured={client.badges?.featured}
|
||||
hwidSupported={client.badges?.hwid}
|
||||
links={links}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
406
src/data/clients.ts
Normal file
406
src/data/clients.ts
Normal file
@@ -0,0 +1,406 @@
|
||||
/**
|
||||
* Clients Data Source
|
||||
*
|
||||
* This file contains all proxy clients information.
|
||||
* Each client can support multiple platforms with platform-specific download links.
|
||||
*
|
||||
* @example
|
||||
* {
|
||||
* id: 'my-client',
|
||||
* name: 'My Client',
|
||||
* core: 'mihomo',
|
||||
* platforms: ['android', 'windows'],
|
||||
* description: 'Amazing proxy client',
|
||||
* badges: {
|
||||
* featured: true, // Shows first on all platforms
|
||||
* hwid: true // Shows HWID badge
|
||||
* },
|
||||
* githubRepo: 'user/repo', // Shows GitHub stars badge
|
||||
* downloadLinks: {
|
||||
* android: 'https://play.google.com/...', // Android-specific download
|
||||
* windows: 'https://github.com/.../releases' // Windows-specific download
|
||||
* },
|
||||
* links: {
|
||||
* github: 'https://github.com/...',
|
||||
* telegram: 'https://t.me/...',
|
||||
* website: 'https://...',
|
||||
* docs: 'https://...'
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
||||
export type CoreType = 'mihomo' | 'other' | 'singbox' | 'xray'
|
||||
export type Platform = 'android' | 'ios' | 'linux' | 'macos' | 'windows'
|
||||
|
||||
export interface Client {
|
||||
author?: string
|
||||
authorLink?: string
|
||||
badges?: {
|
||||
featured?: boolean
|
||||
hwid?: boolean
|
||||
}
|
||||
core: CoreType
|
||||
coreIcon?: string
|
||||
description: string
|
||||
downloadLinks?: {
|
||||
android?: string
|
||||
ios?: string
|
||||
linux?: string
|
||||
macos?: string
|
||||
windows?: string
|
||||
}
|
||||
githubRepo?: string
|
||||
id: string
|
||||
links?: {
|
||||
docs?: string
|
||||
github?: string
|
||||
telegram?: string
|
||||
website?: string
|
||||
}
|
||||
name: string
|
||||
platforms: Platform[]
|
||||
}
|
||||
|
||||
export const CLIENTS: Client[] = [
|
||||
{
|
||||
id: 'happ',
|
||||
name: 'Happ',
|
||||
core: 'xray',
|
||||
platforms: ['android', 'ios', 'macos', 'windows'],
|
||||
description: 'Modern and feature-rich proxy client for Android, iOS, macOS, and Windows.',
|
||||
badges: {
|
||||
featured: true,
|
||||
hwid: true
|
||||
},
|
||||
downloadLinks: {
|
||||
android: 'https://play.google.com/store/apps/details?id=com.happproxy',
|
||||
ios: 'https://apps.apple.com/us/app/happ-proxy-utility/id6504287215',
|
||||
macos: 'https://apps.apple.com/us/app/happ-proxy-utility/id6504287215',
|
||||
windows:
|
||||
'https://github.com/Happ-proxy/happ-desktop/releases/latest/download/setup-Happ.x86.exe',
|
||||
linux: 'https://github.com/Happ-proxy/happ-desktop/releases/'
|
||||
},
|
||||
links: {
|
||||
website: 'https://happ.su/main',
|
||||
telegram: 'https://t.me/happ_chat'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'flclashx',
|
||||
name: 'FlClashX',
|
||||
core: 'mihomo',
|
||||
platforms: ['android', 'windows', 'macos', 'linux'],
|
||||
description: 'Fork of FlClash with improvements and additional features.',
|
||||
badges: {
|
||||
featured: true,
|
||||
hwid: true
|
||||
},
|
||||
githubRepo: 'pluralplay/flclashx',
|
||||
downloadLinks: {
|
||||
android: 'https://github.com/pluralplay/FlClashX/releases',
|
||||
windows: 'https://github.com/pluralplay/FlClashX/releases',
|
||||
macos: 'https://github.com/pluralplay/FlClashX/releases',
|
||||
linux: 'https://github.com/pluralplay/FlClashX/releases'
|
||||
},
|
||||
links: {
|
||||
github: 'https://github.com/pluralplay/FlClashX',
|
||||
telegram: 'https://t.me/flclashx'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'clash-meta-android',
|
||||
name: 'Clash Meta for Android',
|
||||
core: 'mihomo',
|
||||
platforms: ['android'],
|
||||
description: 'A powerful Mihomo-based proxy client for Android',
|
||||
githubRepo: 'MetaCubeX/ClashMetaForAndroid',
|
||||
downloadLinks: {
|
||||
android: 'https://github.com/MetaCubeX/ClashMetaForAndroid/releases'
|
||||
},
|
||||
links: {
|
||||
github: 'https://github.com/MetaCubeX/ClashMetaForAndroid'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'flclash',
|
||||
name: 'FlClash',
|
||||
core: 'mihomo',
|
||||
platforms: ['android', 'windows', 'macos', 'linux'],
|
||||
description: 'Multi-platform proxy client based on ClashMeta, simple and easy to use',
|
||||
githubRepo: 'chen08209/FlClash',
|
||||
downloadLinks: {
|
||||
android: 'https://github.com/chen08209/FlClash/releases',
|
||||
windows: 'https://github.com/chen08209/FlClash/releases',
|
||||
macos: 'https://github.com/chen08209/FlClash/releases',
|
||||
linux: 'https://github.com/chen08209/FlClash/releases'
|
||||
},
|
||||
links: {
|
||||
github: 'https://github.com/chen08209/FlClash'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'clash-mi',
|
||||
name: 'Clash Mi',
|
||||
core: 'mihomo',
|
||||
platforms: ['android', 'ios'],
|
||||
description: 'Mihomo client for Android and iOS (Alpha version)',
|
||||
links: {
|
||||
github: 'https://github.com/placeholder/clash-mi'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'onexray',
|
||||
name: 'OneXray',
|
||||
core: 'xray',
|
||||
platforms: ['android', 'ios', 'windows', 'macos', 'linux'],
|
||||
description: 'Lightweight X-Ray proxy client',
|
||||
githubRepo: 'OneXray/OneXray',
|
||||
downloadLinks: {
|
||||
android: 'https://play.google.com/store/apps/details?id=net.yuandev.onexray',
|
||||
ios: 'https://apps.apple.com/app/onexray/id6503296188',
|
||||
windows: 'https://github.com/OneXray/OneXray/releases',
|
||||
macos: 'https://apps.apple.com/app/onexray/id6503296188',
|
||||
linux: 'https://github.com/OneXray/OneXray/releases'
|
||||
},
|
||||
links: {
|
||||
github: 'https://github.com/OneXray/OneXray'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'v2rayng',
|
||||
name: 'V2rayNG',
|
||||
core: 'xray',
|
||||
platforms: ['android'],
|
||||
description: 'Popular and widely-used V2Ray client for Android',
|
||||
githubRepo: '2dust/v2rayNG',
|
||||
downloadLinks: {
|
||||
android: 'https://github.com/2dust/v2rayNG/releases'
|
||||
},
|
||||
links: {
|
||||
github: 'https://github.com/2dust/v2rayNG'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'v2box',
|
||||
name: 'V2Box',
|
||||
core: 'xray',
|
||||
platforms: ['android', 'ios', 'macos'],
|
||||
description: 'Simple V2Ray client with clean interface',
|
||||
downloadLinks: {
|
||||
android: 'https://play.google.com/store/apps/details?id=dev.hexasoftware.v2box',
|
||||
ios: 'https://apps.apple.com/app/v2box-v2ray-client/id6446814690',
|
||||
macos: 'https://apps.apple.com/app/v2box-v2ray-client/id6446814690'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'simple-gui-client',
|
||||
name: 'Simple GUI Client',
|
||||
core: 'xray',
|
||||
platforms: ['android'],
|
||||
description: 'Minimalistic V2Ray GUI client for Android',
|
||||
author: 'SaeedDev94',
|
||||
authorLink: 'https://github.com/SaeedDev94',
|
||||
githubRepo: 'SaeedDev94/Xray',
|
||||
downloadLinks: {
|
||||
android: 'https://github.com/SaeedDev94/Xray/releases'
|
||||
},
|
||||
links: {
|
||||
github: 'https://github.com/SaeedDev94/Xray'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'simplexray',
|
||||
name: 'SimpleXray',
|
||||
core: 'xray',
|
||||
platforms: ['android'],
|
||||
description: 'Straightforward Xray client for Android',
|
||||
githubRepo: 'lhear/SimpleXray',
|
||||
downloadLinks: {
|
||||
android: 'https://github.com/lhear/SimpleXray/releases'
|
||||
},
|
||||
links: {
|
||||
github: 'https://github.com/lhear/SimpleXray'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'singbox',
|
||||
name: 'sing-box',
|
||||
core: 'singbox',
|
||||
platforms: ['android', 'ios', 'macos'],
|
||||
description: 'Universal proxy platform with multiple protocol support',
|
||||
links: {
|
||||
github: 'https://github.com/placeholder/sing-box',
|
||||
website: 'https://play.google.com/store/apps/placeholder'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'husi',
|
||||
name: 'Husi',
|
||||
core: 'singbox',
|
||||
platforms: ['android'],
|
||||
description: 'Feature-rich Sing-box based proxy client',
|
||||
links: {
|
||||
github: 'https://github.com/placeholder/husi'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'nekobox',
|
||||
name: 'NekoBox',
|
||||
core: 'singbox',
|
||||
platforms: ['android'],
|
||||
description: 'Sing-box based proxy client with modern UI',
|
||||
links: {
|
||||
github: 'https://github.com/placeholder/nekobox'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'karing',
|
||||
name: 'Karing',
|
||||
core: 'singbox',
|
||||
platforms: ['android', 'ios', 'macos', 'windows'],
|
||||
description: 'Multi-platform proxy client based on Sing-box core',
|
||||
links: {
|
||||
github: 'https://github.com/placeholder/karing'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'hiddify',
|
||||
name: 'Hiddify',
|
||||
core: 'singbox',
|
||||
platforms: ['android', 'ios', 'macos', 'windows', 'linux'],
|
||||
description: 'Sing-box client (⚠️ No updates since October 2024)',
|
||||
links: {
|
||||
github: 'https://github.com/placeholder/hiddify'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'prizrakbox',
|
||||
name: 'Prizrak-Box',
|
||||
core: 'mihomo',
|
||||
platforms: ['windows', 'macos', 'linux'],
|
||||
description:
|
||||
'Desktop client with custom routing templates (includes built-in templates for Russia)',
|
||||
githubRepo: 'legiz-ru/Prizrak-Box',
|
||||
badges: {
|
||||
hwid: true
|
||||
},
|
||||
links: {
|
||||
github: 'https://github.com/legiz-ru/Prizrak-Box'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'clash-verge',
|
||||
name: 'Clash Verge',
|
||||
core: 'mihomo',
|
||||
platforms: ['windows', 'macos', 'linux'],
|
||||
description: 'Modern Clash Meta GUI based on Tauri framework',
|
||||
links: {
|
||||
github: 'https://github.com/placeholder/clash-verge'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'koalaclash',
|
||||
name: 'Koala Clash',
|
||||
core: 'mihomo',
|
||||
platforms: ['windows', 'macos', 'linux'],
|
||||
description: 'Fork of Clash Verge Rev with improvements and optimizations.',
|
||||
githubRepo: 'coolcoala/clash-verge-rev-lite',
|
||||
badges: {
|
||||
hwid: true
|
||||
},
|
||||
links: {
|
||||
github: 'https://github.com/coolcoala/clash-verge-rev-lite',
|
||||
telegram: 'https://t.me/+WCL__GOFzZJkYjZi'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'v2rayn',
|
||||
name: 'V2rayN',
|
||||
core: 'xray',
|
||||
platforms: ['windows', 'macos', 'linux'],
|
||||
description: 'Popular V2Ray client.',
|
||||
links: {
|
||||
github: 'https://github.com/placeholder/v2rayn'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'nekoray',
|
||||
name: 'NekoRay',
|
||||
core: 'singbox',
|
||||
platforms: ['windows', 'macos', 'linux'],
|
||||
description: 'Feature-rich Sing-box based client.',
|
||||
links: {
|
||||
github: 'https://github.com/placeholder/nekoray'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
id: 'streisand',
|
||||
name: 'Streisand',
|
||||
core: 'xray',
|
||||
platforms: ['ios', 'macos'],
|
||||
description: 'X-Ray client (⚠️ sometimes has connection issues)',
|
||||
links: {
|
||||
website: 'https://apps.apple.com/placeholder'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'v2raytun',
|
||||
name: 'V2rayTun',
|
||||
core: 'xray',
|
||||
platforms: ['ios', 'macos'],
|
||||
description: 'V2Ray client with tunnel support',
|
||||
links: {
|
||||
website: 'https://apps.apple.com/placeholder'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'stash',
|
||||
name: 'Stash',
|
||||
core: 'other',
|
||||
platforms: ['ios', 'macos'],
|
||||
description: 'Advanced proxy client ($7)',
|
||||
links: {
|
||||
website: 'https://apps.apple.com/placeholder',
|
||||
docs: 'https://placeholder.wiki'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'shadowrocket',
|
||||
name: 'ShadowRocket',
|
||||
core: 'other',
|
||||
platforms: ['ios', 'macos'],
|
||||
description: 'Popular proxy client ($3)',
|
||||
links: {
|
||||
website: 'https://apps.apple.com/placeholder'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'loon',
|
||||
name: 'Loon',
|
||||
core: 'other',
|
||||
platforms: ['ios', 'macos'],
|
||||
description: 'Advanced proxy client with scripting support ($8)',
|
||||
links: {
|
||||
website: 'https://apps.apple.com/placeholder'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
export function getClientsByPlatform(platform: Platform): Client[] {
|
||||
return CLIENTS.filter((client) => client.platforms.includes(platform)).sort((a, b) => {
|
||||
const aFeatured = a.badges?.featured ? 1 : 0
|
||||
const bFeatured = b.badges?.featured ? 1 : 0
|
||||
return bFeatured - aFeatured
|
||||
})
|
||||
}
|
||||
|
||||
export function getPlatformClients() {
|
||||
return {
|
||||
android: getClientsByPlatform('android'),
|
||||
ios: getClientsByPlatform('ios'),
|
||||
windows: getClientsByPlatform('windows'),
|
||||
macos: getClientsByPlatform('macos'),
|
||||
linux: getClientsByPlatform('linux')
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,9 @@ import HeroSection from '@site/src/components/HeroSection'
|
||||
import CategoryNav from '@site/src/components/CategoryNav'
|
||||
import GitHubStars from '@site/src/components/GitHubStars'
|
||||
import ProjectCard from '@site/src/components/ProjectCard'
|
||||
import ClientsList from '@site/src/components/ClientsList'
|
||||
import MDXComponents from '@theme-original/MDXComponents'
|
||||
import ClientCard from '@site/src/components/ClientCard'
|
||||
import StatsBar from '@site/src/components/StatsBar'
|
||||
import Button from '@site/src/components/Button'
|
||||
|
||||
@@ -15,6 +17,8 @@ export default {
|
||||
CategoryNav,
|
||||
GitHubStars,
|
||||
ProjectCard,
|
||||
ClientCard,
|
||||
ClientsList,
|
||||
ProjectsGrid,
|
||||
CategorySection,
|
||||
HeroSection,
|
||||
|
||||
Reference in New Issue
Block a user