Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(website): add tag filtering and searchbar #90

Merged
merged 8 commits into from
Jun 21, 2023
Merged
3 changes: 0 additions & 3 deletions apps/website/content/homepage-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,16 @@ const HOMEPAGE_LIST = [
title: 'Golden Path Templates',
icon: '/images/janus-gpt-image.png',
description: 'Provide your developers with Self Service via Golden Path Templates',
href: '/docs/golden-path-templates',
},
{
title: 'Plugins',
icon: '/images/janus-plugin.png',
description: 'Extend your developer portal with plugins for backstage',
href: '/docs/plugins',
},
{
title: 'Service Catalog',
icon: '/images/janus-hero-image.png',
description: 'Service catalog',
href: '/docs/service-catalog',
},
];

Expand Down
8 changes: 8 additions & 0 deletions apps/website/content/plugin-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const PLUGINS_LIST = [
githubUrl:
'https://raw.githubusercontent.com/janus-idp/backstage-plugins/main/plugins/3scale-backend/README.md',
npmUrl: 'https://www.npmjs.com/package/@janus-idp/backstage-plugin-3scale-backend',
category: 'Backend',
},
{
title: 'Authentication and Authorization with Keycloak',
Expand All @@ -34,6 +35,7 @@ const PLUGINS_LIST = [
githubUrl:
'https://raw.githubusercontent.com/janus-idp/backstage-plugins/main/plugins/keycloak-backend/README.md',
npmUrl: 'https://www.npmjs.com/package/@janus-idp/backstage-plugin-keycloak-backend',
category: 'Backend',
},
{
title: 'Container Image Registry for ACR',
Expand All @@ -43,6 +45,7 @@ const PLUGINS_LIST = [
githubUrl:
'https://raw.githubusercontent.com/janus-idp/backstage-plugins/main/plugins/openshift-image-registry/README.md',
npmUrl: 'https://www.npmjs.com/package/@janus-idp/backstage-plugin-openshift-image-registry',
category: 'Frontend',
},
{
title: 'Container Image Registry for JFrog Artifactory',
Expand All @@ -52,6 +55,7 @@ const PLUGINS_LIST = [
githubUrl:
'https://raw.githubusercontent.com/janus-idp/backstage-plugins/main/plugins/jfrog-artifactory/README.md',
npmUrl: 'https://www.npmjs.com/package/@janus-idp/backstage-plugin-jfrog-artifactory',
category: 'Frontend',
},
{
title: 'Container Image Registry for Quay',
Expand All @@ -61,6 +65,7 @@ const PLUGINS_LIST = [
githubUrl:
'https://raw.githubusercontent.com/janus-idp/backstage-plugins/main/plugins/quay/README.md',
npmUrl: 'https://www.npmjs.com/package/@janus-idp/backstage-plugin-quay',
category: 'Frontend',
},
{
title: 'Multi Cluster View with OCM',
Expand All @@ -70,6 +75,7 @@ const PLUGINS_LIST = [
githubUrl:
'https://raw.githubusercontent.com/janus-idp/backstage-plugins/main/plugins/ocm/README.md',
npmUrl: 'https://www.npmjs.com/package/@janus-idp/backstage-plugin-ocm',
category: 'Frontend',
},
{
title: 'Application Topology for Kubernetes',
Expand All @@ -80,6 +86,7 @@ const PLUGINS_LIST = [
githubUrl:
'https://raw.githubusercontent.com/janus-idp/backstage-plugins/main/plugins/topology/README.md',
npmUrl: 'https://www.npmjs.com/package/@janus-idp/backstage-plugin-topology',
category: 'Frontend',
},
{
title: 'Pipelines with Tekton',
Expand All @@ -89,6 +96,7 @@ const PLUGINS_LIST = [
githubUrl:
'https://raw.githubusercontent.com/janus-idp/backstage-plugins/main/plugins/tekton/README.md',
npmUrl: 'https://www.npmjs.com/package/@janus-idp/backstage-plugin-tekton',
category: 'Frontend',
},
];

Expand Down
4 changes: 3 additions & 1 deletion apps/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@
"clsx": "1.2.1",
"docusaurus-plugin-remote-content": "3.1.0",
"docusaurus-plugin-tailwind": "workspace:*",
"fuse.js": "6.6.2",
"prism-react-renderer": "2.0.5",
"react": "18.2.0",
"react-cookie": "4.1.1",
"react-dom": "18.2.0",
"ui": "workspace:*"
"ui": "workspace:*",
"use-query-params": "2.2.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.4.1",
Expand Down
23 changes: 11 additions & 12 deletions apps/website/src/pages/plugins.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,17 @@

import Link from '@docusaurus/Link';
import Layout from '@theme/Layout';
import Fuse from 'fuse.js';
import React from 'react';
import { PluginsGrid } from 'ui/components/plugin-grid/plugin-grid';
import { PluginsGrid } from 'ui/components';
import PLUGINS_LIST from '../../content/plugin-list';

const pluginsFuse = new Fuse(PLUGINS_LIST, {
threshold: 0.25,
ignoreLocation: true,
keys: [{ name: 'title', weight: 2 }, 'description'],
});

function PluginsHeader(): JSX.Element {
return (
<header className="hero hero--primary relative overflow-hidden px-0 py-16 text-center">
Expand All @@ -39,21 +46,13 @@ function PluginsHeader(): JSX.Element {
);
}

function PluginsPage(): JSX.Element {
export default function Plugins(): JSX.Element {
return (
<>
<Layout title="Plugins" description="Description will go into a meta tag in <head />">
<PluginsHeader />
<main>
<PluginsGrid pluginsList={PLUGINS_LIST} />
<PluginsGrid pluginsFuse={pluginsFuse} pluginsList={PLUGINS_LIST} />
</main>
</>
);
}

export default function Plugins(): JSX.Element {
return (
<Layout title="Plugins" description="Description will go into a meta tag in <head />">
<PluginsPage />
</Layout>
);
}
6 changes: 5 additions & 1 deletion apps/website/src/theme/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import useIsBrowser from '@docusaurus/useIsBrowser';
import React from 'react';
import { CookiesProvider } from 'react-cookie';
import { AnalyticsProvider } from 'ui/providers';
import { QueryParamProvider } from 'use-query-params';
import { ReactRouter5Adapter } from 'use-query-params/adapters/react-router-5';
import { EnvironmentProvider } from '../lib/providers/environment-provider';
import { CustomFields } from '../lib/types';

Expand All @@ -37,7 +39,9 @@ export default function Root({ children }: { children: React.ReactNode }): JSX.E
pathname={location.pathname}
isSSR={!isBrowser}
>
<CookiesProvider>{children}</CookiesProvider>
<CookiesProvider>
<QueryParamProvider adapter={ReactRouter5Adapter}>{children}</QueryParamProvider>
</CookiesProvider>
</AnalyticsProvider>
</EnvironmentProvider>
);
Expand Down
3 changes: 2 additions & 1 deletion apps/website/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@tsconfig/docusaurus/tsconfig.json",
"compilerOptions": {
"baseUrl": "."
"baseUrl": ".",
"checkJs": true
}
}
2 changes: 2 additions & 0 deletions packages/ui/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ export * from './banner/banner';
export * from './homepage-grid/homepage-grid';
export * from './plugin-grid/plugin-grid';
export * from './plugin-header/plugin-header';
export * from './plugin-searchbar/plugin-searchbar';
export * from './tooltip/tooltip';
82 changes: 71 additions & 11 deletions packages/ui/components/plugin-grid/plugin-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@
* limitations under the License.
*/

import { Cog8ToothIcon } from '@heroicons/react/20/solid';
import { motion, useMotionTemplate, useMotionValue } from 'framer-motion';
import React, { useCallback, useContext } from 'react';
import type Fuse from 'fuse.js';
import React, { useCallback, useContext, useMemo, useState } from 'react';
import { FaNodeJs as NodejsIcon, FaReact as ReactIcon } from 'react-icons/fa';
import { StringParam, useQueryParams } from 'use-query-params';
import { EnvironmentContext } from '../../contexts';
import { Plugin } from '../../types';
import { PLUGIN_CATEGORIES, PluginSearchbar } from '../plugin-searchbar/plugin-searchbar';
import { Tooltip } from '../tooltip/tooltip';

type PluginTileProps = Plugin;

function PluginTile({ icon, title, description, href }: PluginTileProps): JSX.Element {
function PluginTile({ icon, title, description, href, category }: PluginTileProps): JSX.Element {
const { Link } = useContext(EnvironmentContext);

const mouseX = useMotionValue(0);
Expand All @@ -39,7 +45,7 @@ function PluginTile({ icon, title, description, href }: PluginTileProps): JSX.El

return (
<Link
className="dark:bg-pf-cyan-300/[0.15] bg-pf-cyan-50 group relative flex max-w-md flex-col items-center justify-between rounded-xl border border-white/10 p-4 text-center shadow hover:no-underline dark:shadow-2xl"
className="dark:border-pf-cyan-300/30 border-pf-cyan-300/20 dark:bg-pf-cyan-300/[0.15] bg-pf-cyan-50 dark:shadow-2x ring-pf-cyan-300 group relative flex flex-col items-center justify-between rounded-xl border border-solid px-8 py-4 shadow shadow-black/20 outline-0 transition-shadow hover:no-underline hover:shadow-md focus:ring-1 dark:shadow-black/50"
href={href}
onMouseMove={handleMouseMove}
>
Expand All @@ -55,25 +61,79 @@ function PluginTile({ icon, title, description, href }: PluginTileProps): JSX.El
`,
}}
/>
<div>
<img className="mb-2 h-[75px] w-[75px]" src={icon} alt={`${title} icon`} />
<h3>{title}</h3>
<p>{description}</p>
<div className="flex h-full flex-col justify-between">
<div>
<img className="mb-4 h-[75px] w-[75px]" src={icon} alt={`${title} icon`} />
<h3>{title}</h3>
<p className="line-clamp-3">{description}</p>
</div>
<div className="flex flex-row-reverse">
{/* Node.js brand guidelines require us to use a white icon on a dark background and vice versa */}
{category === 'Backend' && (
<Tooltip
buttonContent={<NodejsIcon className="h-8 w-auto text-black dark:text-white" />}
popupContent={category}
/>
)}
{category === 'Frontend' && (
<Tooltip
buttonContent={<ReactIcon className="h-8 w-auto text-black dark:text-white" />}
popupContent={category}
/>
)}
{category === 'Custom Actions' && (
<Tooltip
buttonContent={<Cog8ToothIcon className="h-8 w-auto text-black dark:text-white" />}
popupContent={category}
/>
)}
</div>
</div>
<div className="flex items-center justify-center pb-4">Learn more!</div>
</Link>
);
}

type PluginsFeaturesProps = {
pluginsFuse: Fuse<Plugin>;
pluginsList: Plugin[];
};

export function PluginsGrid({ pluginsList }: PluginsFeaturesProps): JSX.Element {
export function PluginsGrid({ pluginsFuse, pluginsList }: PluginsFeaturesProps): JSX.Element {
// search state cannot be lifted to query params because docusaurus uses react router v5
// which does not support a shallow updates. i.e. every time the state changes, the page
// will lose focus on the input field.
const [queryParams, setQueryParams] = useQueryParams({
category: StringParam,
});
const [search, setSearch] = useState('');

const pluginCategory =
PLUGIN_CATEGORIES.find(({ slug }) => slug === queryParams.category) || PLUGIN_CATEGORIES[0];

const plugins = useMemo(() => {
let p = pluginsList;

if (search !== '') {
p = pluginsFuse.search(search).map(({ item }) => item);
}

if (pluginCategory.name !== 'All plugins') {
p = p.filter(({ category }) => category === pluginCategory.name);
}

return p;
}, [pluginCategory.name, pluginsFuse, pluginsList, search]);

return (
<section className="container flex w-full p-8">
<section className="container flex w-full flex-col p-8">
<PluginSearchbar
search={search}
setSearch={setSearch}
setQueryParams={setQueryParams}
pluginCategory={pluginCategory}
/>
<div className="grid gap-4 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
{pluginsList.map((plugin) => (
{plugins.map((plugin) => (
<PluginTile key={plugin.title} {...plugin} />
))}
</div>
Expand Down
Loading