+
+ );
+}
diff --git a/packages/ui/components/gpt-header/gpt-header.tsx b/packages/ui/components/gpt-header/gpt-header.tsx
new file mode 100644
index 00000000..c922d432
--- /dev/null
+++ b/packages/ui/components/gpt-header/gpt-header.tsx
@@ -0,0 +1,40 @@
+/**
+ * Copyright 2023 Janus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useContext } from 'react';
+import { EnvironmentContext } from '../../contexts';
+import { GPT } from '../../types';
+
+export type GPTHeaderProps = {
+ remoteContent: GPT;
+};
+
+export function GPTHeader(props: GPTHeaderProps): JSX.Element {
+ const { remoteContent } = props;
+ const { Link } = useContext(EnvironmentContext);
+
+ return (
+
+
+
{remoteContent.title}
+
{remoteContent.description}
+
+
+ Github Repo
+
+
+ );
+}
diff --git a/packages/ui/components/index.tsx b/packages/ui/components/index.tsx
index 632a7653..672e377a 100644
--- a/packages/ui/components/index.tsx
+++ b/packages/ui/components/index.tsx
@@ -15,8 +15,12 @@
*/
export * from './banner/banner';
+export * from './gpt-grid/gpt-grid';
+export * from './gpt-header/gpt-header';
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 './searchbar/searchbar';
+export * from './searchbar/types';
+export * from './tile/tile';
export * from './tooltip/tooltip';
diff --git a/packages/ui/components/plugin-grid/plugin-grid.tsx b/packages/ui/components/plugin-grid/plugin-grid.tsx
index 99b9b9b3..9b3260ec 100644
--- a/packages/ui/components/plugin-grid/plugin-grid.tsx
+++ b/packages/ui/components/plugin-grid/plugin-grid.tsx
@@ -15,122 +15,84 @@
*/
import { Cog8ToothIcon } from '@heroicons/react/20/solid';
-import { motion, useMotionTemplate, useMotionValue } from 'framer-motion';
import type Fuse from 'fuse.js';
-import React, { useCallback, useContext, useMemo, useState } from 'react';
+import React 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 { useSearch } from '../../hooks';
import { Plugin } from '../../types';
-import { PLUGIN_CATEGORIES, PluginSearchbar } from '../plugin-searchbar/plugin-searchbar';
+import { Searchbar } from '../searchbar/searchbar';
+import { PLUGIN_CATEGORIES } from '../searchbar/types';
+import { Tile } from '../tile/tile';
import { Tooltip } from '../tooltip/tooltip';
type PluginTileProps = Plugin;
function PluginTile({ icon, title, description, href, category }: PluginTileProps): JSX.Element {
- const { Link } = useContext(EnvironmentContext);
-
- const mouseX = useMotionValue(0);
- const mouseY = useMotionValue(0);
-
- const handleMouseMove = useCallback(
- ({ currentTarget, clientX, clientY }: React.MouseEvent) => {
- const { left, top } = currentTarget.getBoundingClientRect();
-
- mouseX.set(clientX - left);
- mouseY.set(clientY - top);
- },
- [mouseX, mouseY],
- );
-
return (
-
-
-
-
-
-
{title}
-
{description}
-
-
- {/* Node.js brand guidelines require us to use a white icon on a dark background and vice versa */}
- {category === 'Backend' && (
- }
- popupContent={category}
- />
- )}
- {category === 'Frontend' && (
- }
- popupContent={category}
- />
- )}
- {category === 'Custom Actions' && (
- }
- popupContent={category}
- />
- )}
-
+
+
+
+
{title}
+
{description}
-
+
+ {/* Node.js brand guidelines require us to use a white icon on a dark background and vice versa */}
+ {category === 'Backend' && (
+
+ }
+ popupContent={category}
+ />
+ )}
+ {category === 'Frontend' && (
+
+ }
+ popupContent={category}
+ />
+ )}
+ {category === 'Custom Actions' && (
+
+ }
+ popupContent={category}
+ />
+ )}
+
+
);
}
type PluginsFeaturesProps = {
- pluginsFuse: Fuse;
- pluginsList: Plugin[];
+ PLUGIN_FUSE: Fuse;
+ PLUGIN_LIST: Plugin[];
};
-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,
+export function PluginsGrid({ PLUGIN_FUSE, PLUGIN_LIST }: PluginsFeaturesProps): JSX.Element {
+ const {
+ content: plugins,
+ category,
+ search,
+ setSearch,
+ setQueryParams,
+ } = useSearch({
+ CATEGORIES: PLUGIN_CATEGORIES,
+ CONTENT_FUSE: PLUGIN_FUSE,
+ CONTENT_LIST: PLUGIN_LIST,
});
- 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 (
-
+
+
+
+ )}
+
+
+ );
+}
diff --git a/packages/ui/components/searchbar/types.ts b/packages/ui/components/searchbar/types.ts
new file mode 100644
index 00000000..c9978d49
--- /dev/null
+++ b/packages/ui/components/searchbar/types.ts
@@ -0,0 +1,31 @@
+/**
+ * Copyright 2023 Janus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { PluginCategoryKind } from '../../types';
+
+export type PluginCategory = {
+ name: PluginCategoryKind | 'All plugins';
+ slug: string | null;
+};
+
+export const PLUGIN_CATEGORIES: PluginCategory[] = [
+ { name: 'All plugins', slug: null },
+ { name: 'Frontend', slug: 'frontend' },
+ { name: 'Backend', slug: 'backend' },
+ { name: 'Custom Actions', slug: 'custom-actions' },
+];
+
+export type CategoryOptions = PluginCategory;
diff --git a/packages/ui/components/tile/tile.tsx b/packages/ui/components/tile/tile.tsx
new file mode 100644
index 00000000..beff059c
--- /dev/null
+++ b/packages/ui/components/tile/tile.tsx
@@ -0,0 +1,64 @@
+/**
+ * Copyright 2023 Janus Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { motion, useMotionTemplate, useMotionValue } from 'framer-motion';
+import React, { useCallback, useContext, type PropsWithChildren } from 'react';
+import { EnvironmentContext } from '../../contexts';
+
+export type TileProps = {
+ href: string;
+};
+
+export function Tile({ href, children }: PropsWithChildren): JSX.Element {
+ const { Link } = useContext(EnvironmentContext);
+
+ const mouseX = useMotionValue(0);
+ const mouseY = useMotionValue(0);
+
+ const handleMouseMove = useCallback(
+ ({ currentTarget, clientX, clientY }: React.MouseEvent) => {
+ const { left, top } = currentTarget.getBoundingClientRect();
+
+ mouseX.set(clientX - left);
+ mouseY.set(clientY - top);
+ },
+ [mouseX, mouseY],
+ );
+
+ return (
+
+
+