diff --git a/example/empty-plugin.ts b/example/empty-plugin.ts index 3e45f86..d4f4ef4 100644 --- a/example/empty-plugin.ts +++ b/example/empty-plugin.ts @@ -3,6 +3,12 @@ import { Plugin, PluginKey } from "prosemirror-state"; function createPluginState() { return { doSomething() {}, + apple: "apple", + cherry: "cherry", + dog: "dog", + elephant: "elephant", + frog: "frog", + banana: "banana", }; } diff --git a/src/components/button.tsx b/src/components/button.tsx new file mode 100644 index 0000000..4c876be --- /dev/null +++ b/src/components/button.tsx @@ -0,0 +1,31 @@ +import React, { ReactNode, MouseEventHandler } from "react"; +import { css } from "@compiled/react"; +import theme from "../theme"; + +type ButtonProps = { + onClick: MouseEventHandler; + children: ReactNode; +}; + +const buttonStyles = css({ + color: theme.softerMain, + marginTop: "4px", + marginBottom: "4px", + fontWeight: 400, + fontSize: "12px", + background: "transparent", + border: "none", + "&:hover": { + background: theme.white10, + }, +}); + +const Button = ({ onClick, children }: ButtonProps) => { + return ( + + ); +}; + +export default Button; diff --git a/src/components/json-tree.tsx b/src/components/json-tree.tsx index 6521452..5153d68 100644 --- a/src/components/json-tree.tsx +++ b/src/components/json-tree.tsx @@ -24,6 +24,7 @@ type JSONTreeProps = { valueRenderer?: (value: any) => string | React.ReactNode; labelRenderer?: (value: any) => string | React.ReactNode; isCustomNode?: (node: any) => boolean; + sortObjectKeys?: boolean | ((...args: any[]) => any); }; export default function JSONTreeWrapper(props: JSONTreeProps) { return ( diff --git a/src/components/search-bar.tsx b/src/components/search-bar.tsx new file mode 100644 index 0000000..6ed6714 --- /dev/null +++ b/src/components/search-bar.tsx @@ -0,0 +1,66 @@ +import React, { useCallback, useState } from "react"; +import theme from "../theme"; +import { css } from "@compiled/react"; + +interface SearchBarProps { + onSearch: (query: string) => void; +} + +const buttonStyles = css({ + color: theme.softerMain, + padding: 6, + fontWeight: 400, + fontSize: "12px", + background: "transparent", + border: "none", + "&:hover": { + background: theme.white10, + }, +}); + +const inputStyles = css({ + background: "transparent", + border: "1px solid " + theme.softerMain, + outline: "none", + color: theme.softerMain, + "&::placeholder": { color: theme.white20 }, +}); + +const searchBarWrapperStyles = css({ + display: "flex", + gap: "4px", + margin: 4, +}); + +const SearchBar: React.FC = ({ onSearch }) => { + const [query, setQuery] = useState(""); + + const handleInputChange = useCallback( + (event: React.ChangeEvent) => { + setQuery(event.target.value); + onSearch(event.target.value); + }, + [onSearch] + ); + + const handleSearch = useCallback(() => { + onSearch(query); + }, [query, onSearch]); + + return ( +
+ + +
+ ); +}; + +export default SearchBar; diff --git a/src/tabs/plugins.tsx b/src/tabs/plugins.tsx index ba3ef27..1028784 100644 --- a/src/tabs/plugins.tsx +++ b/src/tabs/plugins.tsx @@ -1,12 +1,14 @@ -import React from "react"; +import React, { useCallback, useState } from "react"; import type { Plugin } from "prosemirror-state"; import { InfoPanel } from "../components/info-panel"; import { Heading } from "../components/heading"; import JSONTree from "../components/json-tree"; import { List } from "../components/list"; import { SplitView, SplitViewCol } from "../components/split-view"; -import { atom, useAtom, useAtomValue } from "jotai"; +import { useAtomValue } from "jotai"; import { editorStateAtom } from "../state/editor-state"; +import SearchBar from "../components/search-bar"; +import Button from "../components/button"; export function valueRenderer(raw: string, ...rest: Array) { if (typeof rest[0] === "function") { @@ -19,40 +21,95 @@ export function PluginState(props: { pluginState: any }) { return (
Plugin State - +
); } -const selectedPluginIndexAtom = atom(0); - // TODO: replace isDimmed with useCallback once EditorStateContainer is decomposed export default function PluginsTab() { - const [selectedIndex, setSelected] = useAtom(selectedPluginIndexAtom); - const isSelected = React.useCallback( - (_plugin, index) => selectedIndex === index, - [selectedIndex] - ); + const state = useAtomValue(editorStateAtom); + if (!state) return null; + + const [selectedPlugin, setSelectedPlugin] = useState(state.plugins[0]); + const [pluginsLocal, setPluginsLocal] = useState(state.plugins); + const [sortAsc, setSortOrder] = useState(true); + const handleOnListItemClick = React.useCallback( - (_plugin, index) => setSelected(index), + (_plugin) => setSelectedPlugin(_plugin), [] ); - const state = useAtomValue(editorStateAtom); - if (!state) return null; - - const plugins = state.plugins as any as Plugin[]; - const selectedPlugin = plugins[selectedIndex]; const selectedPluginState = selectedPlugin.getState(state); + const handleSearch = useCallback( + (input: string) => { + const filteredPlugins = (state.plugins as any as Plugin[]).filter( + (plugin) => { + return (plugin as any).key + .toLowerCase() + .includes(input.toLowerCase()); + } + ); + setPluginsLocal(filteredPlugins); + }, + [state.plugins] + ); + + const handleClickSort = () => { + setSortOrder(!sortAsc); + }; + + const handleSortAsc = (plugins: any) => { + return [...plugins].sort((a, b) => { + if ((a as any).key < (b as any).key) { + return -1; + } + if ((a as any).key > (b as any).key) { + return 1; + } + return 0; + }); + }; + const handleSortDes = (plugins: any) => { + return [...plugins].sort((a, b) => { + if ((a as any).key < (b as any).key) { + return 1; + } + if ((a as any).key > (b as any).key) { + return -1; + } + return 0; + }); + }; + return ( +
+ + +
(plugin as any).key} title={(plugin: Plugin) => (plugin as any).key} - isSelected={isSelected} isDimmed={(plugin: Plugin) => !plugin.getState(state)} onListItemClick={handleOnListItemClick} />