nx/nx-dev/ui-community/src/lib/plugin-directory.tsx

246 lines
11 KiB
TypeScript

import {
ArrowDownIcon,
ArrowLongDownIcon,
ArrowLongUpIcon,
ClockIcon,
StarIcon,
} from '@heroicons/react/24/outline';
import { MagnifyingGlassIcon } from '@heroicons/react/24/solid';
import { PluginCard, SectionHeading } from '@nx/nx-dev/ui-common';
import { useState } from 'react';
interface Plugin {
description: string;
name: string;
url: string;
isOfficial: boolean;
lastPublishedDate?: string;
npmDownloads?: string;
githubStars?: string;
nxVersion?: string;
}
type OrderByStatus =
| 'lastPublishDate'
| 'npmDownloads'
| 'githubStars'
| 'nxVersion'
| undefined;
interface Modifiers {
term: string;
officialStatus: 'official' | 'community' | undefined;
minimumDownloads: number | undefined;
minimumStars: number | undefined;
minimumNxVersion: string | undefined;
orderBy: OrderByStatus;
orderDirection: 'ASC' | 'DESC';
}
export function PluginDirectory({
pluginList,
}: {
pluginList: Plugin[];
}): JSX.Element {
const [modifiers, setModifiers] = useState<Modifiers>({
term: '',
officialStatus: undefined,
minimumDownloads: undefined,
minimumStars: undefined,
minimumNxVersion: undefined,
orderBy: undefined,
orderDirection: 'ASC',
});
function setOrderBy(status: OrderByStatus) {
if (modifiers.orderBy === status) {
setModifiers({
...modifiers,
orderDirection: modifiers.orderDirection === 'ASC' ? 'DESC' : 'ASC',
});
} else {
setModifiers({
...modifiers,
orderBy: status,
});
}
}
return (
<div id="plugin-directory">
<div className="flex w-full flex-col justify-between gap-8 md:flex-row ">
<SectionHeading as="h2" variant="display" id="plugins-registry">
<span className="whitespace-nowrap">Nx Plugins</span> Registry
</SectionHeading>
<div>
<label htmlFor="search" className="sr-only">
Quick search
</label>
<div className="relative">
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<MagnifyingGlassIcon className="h-5 w-5 text-slate-500" />
</div>
<input
id="search"
name="search"
className="block w-full rounded-md border border-slate-300 bg-white py-2 pl-10 pr-3 text-sm placeholder-slate-500 transition focus:placeholder-slate-400 dark:border-slate-900 dark:bg-slate-700"
placeholder="Quick search"
onChange={(event) =>
setModifiers({ ...modifiers, term: event.target.value })
}
type="search"
/>
</div>
<div className="text-xs my-2 flex whitespace-nowrap">
<div className="py-1 mr-1">Order by:</div>
<div className="flex flex-wrap gap-1">
<button
className="rounded-sm border border-slate-200 bg-white py-1 px-1 font-semibold transition hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-800 dark:hover:bg-slate-700"
onClick={() => setOrderBy('lastPublishDate')}
>
<ClockIcon className="h-4 w-4 inline-block mr-0.5 align-bottom"></ClockIcon>
Release Date
{modifiers.orderBy === 'lastPublishDate' &&
modifiers.orderDirection === 'DESC' ? (
<ArrowLongUpIcon className="h-4 w-4 inline-block ml-0.5 align-bottom"></ArrowLongUpIcon>
) : null}
{modifiers.orderBy === 'lastPublishDate' &&
modifiers.orderDirection === 'ASC' ? (
<ArrowLongDownIcon className="h-4 w-4 inline-block ml-0.5 align-bottom"></ArrowLongDownIcon>
) : null}
</button>
<button
className="rounded-sm border border-slate-200 bg-white py-1 px-1 font-semibold transition hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-800 dark:hover:bg-slate-700"
onClick={() => setOrderBy('npmDownloads')}
>
<ArrowDownIcon className="h-4 w-4 inline-block mr-0.5 align-bottom"></ArrowDownIcon>
Downloads
{modifiers.orderBy === 'npmDownloads' &&
modifiers.orderDirection === 'DESC' ? (
<ArrowLongUpIcon className="h-4 w-4 inline-block ml-0.5 align-bottom"></ArrowLongUpIcon>
) : null}
{modifiers.orderBy === 'npmDownloads' &&
modifiers.orderDirection === 'ASC' ? (
<ArrowLongDownIcon className="h-4 w-4 inline-block ml-0.5 align-bottom"></ArrowLongDownIcon>
) : null}
</button>
<button
className="rounded-sm border border-slate-200 bg-white py-1 px-1 font-semibold transition hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-800 dark:hover:bg-slate-700"
onClick={() => setOrderBy('githubStars')}
>
<StarIcon className="h-4 w-4 inline-block mr-0.5 align-bottom"></StarIcon>
GH Stars
{modifiers.orderBy === 'githubStars' &&
modifiers.orderDirection === 'DESC' ? (
<ArrowLongUpIcon className="h-4 w-4 inline-block ml-0.5 align-bottom"></ArrowLongUpIcon>
) : null}
{modifiers.orderBy === 'githubStars' &&
modifiers.orderDirection === 'ASC' ? (
<ArrowLongDownIcon className="h-4 w-4 inline-block ml-0.5 align-bottom"></ArrowLongDownIcon>
) : null}
</button>
<button
className="rounded-sm border border-slate-200 bg-white py-1 px-1 font-semibold transition hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-800 dark:hover:bg-slate-700"
onClick={() => setOrderBy('nxVersion')}
>
{/* Nx Logo */}
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
className="h-4 w-4 inline-block mx-0.5 align-bottom"
fill="currentColor"
>
<title>Nx</title>
<path d="M11.987 14.138l-3.132 4.923-5.193-8.427-.012 8.822H0V4.544h3.691l5.247 8.833.005-3.998 3.044 4.759zm.601-5.761c.024-.048 0-3.784.008-3.833h-3.65c.002.059-.005 3.776-.003 3.833h3.645zm5.634 4.134a2.061 2.061 0 0 0-1.969 1.336 1.963 1.963 0 0 1 2.343-.739c.396.161.917.422 1.33.283a2.1 2.1 0 0 0-1.704-.88zm3.39 1.061c-.375-.13-.8-.277-1.109-.681-.06-.08-.116-.17-.176-.265a2.143 2.143 0 0 0-.533-.642c-.294-.216-.68-.322-1.18-.322a2.482 2.482 0 0 0-2.294 1.536 2.325 2.325 0 0 1 4.002.388.75.75 0 0 0 .836.334c.493-.105.46.36 1.203.518v-.133c-.003-.446-.246-.55-.75-.733zm2.024 1.266a.723.723 0 0 0 .347-.638c-.01-2.957-2.41-5.487-5.37-5.487a5.364 5.364 0 0 0-4.487 2.418c-.01-.026-1.522-2.39-1.538-2.418H8.943l3.463 5.423-3.379 5.32h3.54l1.54-2.366 1.568 2.366h3.541l-3.21-5.052a.7.7 0 0 1-.084-.32 2.69 2.69 0 0 1 2.69-2.691h.001c1.488 0 1.736.89 2.057 1.308.634.826 1.9.464 1.9 1.541a.707.707 0 0 0 1.066.596zm.35.133c-.173.372-.56.338-.755.639-.176.271.114.412.114.412s.337.156.538-.311c.104-.231.14-.488.103-.74z" />
</svg>
Nx Version
{modifiers.orderBy === 'nxVersion' &&
modifiers.orderDirection === 'DESC' ? (
<ArrowLongUpIcon className="h-4 w-4 inline-block ml-0.5 align-bottom"></ArrowLongUpIcon>
) : null}
{modifiers.orderBy === 'nxVersion' &&
modifiers.orderDirection === 'ASC' ? (
<ArrowLongDownIcon className="h-4 w-4 inline-block ml-0.5 align-bottom"></ArrowLongDownIcon>
) : null}
</button>
</div>
</div>
</div>
</div>
<div className="my-12 grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3">
{pluginList
.filter((plugin) =>
!!modifiers.term
? plugin.name
.toLowerCase()
.includes(modifiers.term.toLowerCase()) ||
plugin.description
.toLowerCase()
.includes(modifiers.term.toLowerCase())
: true
)
.sort((a, b) => {
if (modifiers.orderBy === 'lastPublishDate') {
return (
(modifiers.orderDirection === 'ASC' ? 1 : -1) *
(new Date(a.lastPublishedDate || '').getTime() -
new Date(b.lastPublishedDate || '').getTime())
);
} else if (modifiers.orderBy === 'npmDownloads') {
return (
(modifiers.orderDirection === 'ASC' ? 1 : -1) *
(Number.parseInt(a.npmDownloads || '0') -
Number.parseInt(b.npmDownloads || '0'))
);
} else if (modifiers.orderBy === 'githubStars') {
return (
(modifiers.orderDirection === 'ASC' ? 1 : -1) *
(Number.parseInt(a.githubStars || '0') -
Number.parseInt(b.githubStars || '0'))
);
} else if (modifiers.orderBy === 'nxVersion') {
const versionValueMap: Record<string, number> = {
unknown: 0,
'12': 12,
'13': 13,
'14': 14,
'15': 15,
'16': 16,
'17': 17,
'18': 18,
'>= 15': 17,
'>= 14': 16,
'>= 13': 15,
official: 1000,
};
function getValueFromVersion(version: string = 'unknown') {
const mapKey =
Object.keys(versionValueMap).find((key) =>
version.startsWith(key)
) || 'unknown';
return versionValueMap[mapKey];
}
return (
(modifiers.orderDirection === 'ASC' ? 1 : -1) *
(getValueFromVersion(a.nxVersion) -
getValueFromVersion(b.nxVersion))
);
}
return 0;
})
.map((plugin) => (
<PluginCard
key={plugin.name}
name={plugin.name}
description={plugin.description}
isOfficial={plugin.isOfficial}
lastPublishedDate={plugin.lastPublishedDate}
npmDownloads={plugin.npmDownloads}
githubStars={plugin.githubStars}
nxVersion={plugin.nxVersion}
url={plugin.url}
/>
))}
</div>
</div>
);
}