diff --git a/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/SchemaEdgesStyling.tsx b/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/SchemaEdgesStyling.tsx
new file mode 100644
index 000000000..58683761b
--- /dev/null
+++ b/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/SchemaEdgesStyling.tsx
@@ -0,0 +1,43 @@
+import { Virtuoso } from "react-virtuoso";
+import { Fragment } from "react/jsx-runtime";
+
+import {
+ Divider,
+ Panel,
+ PanelContent,
+ PanelHeader,
+ PanelTitle,
+} from "@/components";
+import { useDisplayEdgeTypeConfigs } from "@/core";
+import { useTranslations } from "@/hooks";
+import SingleEdgeStyling from "@/modules/EdgesStyling/SingleEdgeStyling";
+
+/** Styling panel for edge types in the schema explorer sidebar. */
+export function SchemaEdgesStyling() {
+ const etConfigMap = useDisplayEdgeTypeConfigs();
+ const etConfigs = etConfigMap.values().toArray();
+ const t = useTranslations();
+
+ return (
+
+
+ {t("edges-styling.title")}
+
+
+ (
+
+ {index !== 0 ? : null}
+
+
+ )}
+ />
+
+
+ );
+}
diff --git a/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/SchemaExplorerSidebar.test.tsx b/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/SchemaExplorerSidebar.test.tsx
new file mode 100644
index 000000000..496410221
--- /dev/null
+++ b/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/SchemaExplorerSidebar.test.tsx
@@ -0,0 +1,94 @@
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { Provider } from "jotai";
+import { describe, expect, test, vi } from "vitest";
+
+import { TooltipProvider } from "@/components";
+import { getAppStore } from "@/core";
+import {
+ createTestableEdge,
+ createTestableVertex,
+ DbState,
+} from "@/utils/testing";
+
+import { SchemaExplorerSidebar } from "./SchemaExplorerSidebar";
+
+vi.mock("react-virtuoso", () => ({
+ Virtuoso: ({
+ data,
+ itemContent,
+ }: {
+ data: unknown[];
+ itemContent: (index: number, item: unknown) => React.ReactNode;
+ }) => data?.map((item, index) => itemContent(index, item)),
+}));
+
+function renderSidebar(state: DbState) {
+ const store = getAppStore();
+ state.applyTo(store);
+
+ const queryClient = new QueryClient({
+ defaultOptions: { queries: { retry: false } },
+ });
+
+ return render(
+
+
+
+
+
+
+ ,
+ );
+}
+
+describe("SchemaExplorerSidebar", () => {
+ test("renders details tab by default with empty selection message", () => {
+ const state = new DbState();
+ renderSidebar(state);
+
+ expect(screen.getByText("Empty Selection")).toBeInTheDocument();
+ });
+
+ test("shows three sidebar tabs", () => {
+ const state = new DbState();
+ renderSidebar(state);
+
+ const tabs = screen.getAllByRole("tab");
+ expect(tabs).toHaveLength(3);
+ });
+
+ test("renders node styling content when clicking second tab", async () => {
+ const user = userEvent.setup();
+ const state = new DbState();
+ const vertex = createTestableVertex().with({ types: ["Airport"] });
+ state.addTestableVertexToGraph(vertex);
+
+ renderSidebar(state);
+
+ const tabs = screen.getAllByRole("tab");
+ await user.click(tabs[1]);
+
+ expect(screen.getByText("Airport")).toBeInTheDocument();
+ });
+
+ test("renders edge styling content when clicking third tab", async () => {
+ const user = userEvent.setup();
+ const state = new DbState();
+ const source = createTestableVertex().with({ types: ["Airport"] });
+ const target = createTestableVertex().with({ types: ["City"] });
+ const edge = createTestableEdge()
+ .with({ type: "locatedIn" })
+ .withSource(source)
+ .withTarget(target);
+ state.addTestableEdgeToGraph(edge);
+
+ renderSidebar(state);
+
+ const tabs = screen.getAllByRole("tab");
+ await user.click(tabs[2]);
+
+ expect(screen.getByText("locatedIn")).toBeInTheDocument();
+ });
+});
diff --git a/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/SchemaExplorerSidebar.tsx b/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/SchemaExplorerSidebar.tsx
index d8892e319..f01d758ea 100644
--- a/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/SchemaExplorerSidebar.tsx
+++ b/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/SchemaExplorerSidebar.tsx
@@ -2,30 +2,37 @@ import { Tabs as TabsPrimitive } from "radix-ui";
import { Resizable } from "re-resizable";
import { type PropsWithChildren, useState } from "react";
-import { DetailsIcon } from "@/components";
+import { DetailsIcon, EdgeIcon, GraphIcon } from "@/components";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/Tooltip";
+import { useTranslations } from "@/hooks";
import { cn, LABELS } from "@/utils";
import type { SchemaGraphSelection } from "../SchemaGraph";
import { SchemaDetailsContent } from "./SchemaDetailsContent";
+import { SchemaEdgesStyling } from "./SchemaEdgesStyling";
import {
DEFAULT_SCHEMA_SIDEBAR_WIDTH,
useSchemaExplorerSidebarSize,
} from "./schemaExplorerLayout";
+import { SchemaNodesStyling } from "./SchemaNodesStyling";
export type SchemaExplorerSidebarProps = {
selection: SchemaGraphSelection;
};
-/** Resizable sidebar for schema graph with details tab */
+/** Resizable sidebar for schema graph with details, node styling, and edge styling tabs */
export function SchemaExplorerSidebar({
selection,
}: SchemaExplorerSidebarProps) {
+ const t = useTranslations();
+ const [activeTab, setActiveTab] = useState("details");
+
return (
@@ -36,10 +43,28 @@ export function SchemaExplorerSidebar({
>
+
+
+
+
+
+
+
+
+
+
+
+
);
diff --git a/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/SchemaNodesStyling.tsx b/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/SchemaNodesStyling.tsx
new file mode 100644
index 000000000..3eeaff714
--- /dev/null
+++ b/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/SchemaNodesStyling.tsx
@@ -0,0 +1,42 @@
+import { Virtuoso } from "react-virtuoso";
+import { Fragment } from "react/jsx-runtime";
+
+import {
+ Divider,
+ Panel,
+ PanelContent,
+ PanelHeader,
+ PanelTitle,
+} from "@/components";
+import { useDisplayVertexTypeConfigs } from "@/core";
+import { useTranslations } from "@/hooks";
+import SingleNodeStyling from "@/modules/NodesStyling/SingleNodeStyling";
+
+/** Styling panel for node types in the schema explorer sidebar. */
+export function SchemaNodesStyling() {
+ const vtConfigs = useDisplayVertexTypeConfigs().values().toArray();
+ const t = useTranslations();
+
+ return (
+
+
+ {t("nodes-styling.title")}
+
+
+ (
+
+ {index !== 0 ? : null}
+
+
+ )}
+ />
+
+
+ );
+}
diff --git a/packages/graph-explorer/src/routes/SchemaExplorer/SchemaExplorer.tsx b/packages/graph-explorer/src/routes/SchemaExplorer/SchemaExplorer.tsx
index 09a0757f0..dc900d773 100644
--- a/packages/graph-explorer/src/routes/SchemaExplorer/SchemaExplorer.tsx
+++ b/packages/graph-explorer/src/routes/SchemaExplorer/SchemaExplorer.tsx
@@ -9,6 +9,8 @@ import {
} from "@/components";
import { GraphProvider } from "@/components/Graph";
import { useConfiguration } from "@/core";
+import { EdgeStyleDialog } from "@/modules/EdgesStyling";
+import { NodeStyleDialog } from "@/modules/NodesStyling";
import { SchemaGraph } from "@/modules/SchemaGraph";
export default function SchemaExplorer() {
@@ -31,6 +33,8 @@ export default function SchemaExplorer() {
+
+