From a1456e17756746d31a0ae1c6a80ec42a40ca419e Mon Sep 17 00:00:00 2001 From: Kris McGinnes Date: Fri, 26 Jun 2026 15:22:16 -0500 Subject: [PATCH] Restyle Settings pages and add shared UI components Replaces SettingsSection with a SettingsPage layout (page header with icon, title, description) and redesigns the General and About pages plus the sidebar with grouped sections, lucide icons, and updated spacing and color tokens. Adds shared components: - AlertDialog: radix-based confirmation modal with media/header/footer slots and a primary-danger Button variant. - Group: bordered item list with variant support (default, danger, success, warning) flowing via context. - LabelledSetting: label/description row that associates with its control via htmlFor and reflects the disabled state. Page and section titles render as h1/h2 for proper heading semantics. Also keeps "Refresh Schema" on one line with a non-breaking space. --- .../src/components/AlertDialog.tsx | 202 ++++++++++++++ .../src/components/Button/Button.tsx | 2 + .../graph-explorer/src/components/Group.tsx | 89 ++++++ .../src/components/LabelledSetting.tsx | 31 +++ .../graph-explorer/src/components/NavBar.tsx | 5 +- .../src/components/SettingsPage.tsx | 75 +++++ .../src/components/SettingsSection.tsx | 123 --------- .../graph-explorer/src/components/index.ts | 5 +- packages/graph-explorer/src/index.css | 14 +- .../SchemaGraph/SchemaGraphToolbar.tsx | 4 +- .../src/routes/Settings/SettingsAbout.tsx | 83 ++++-- .../src/routes/Settings/SettingsGeneral.tsx | 258 +++++++++++------- .../src/routes/Settings/SettingsRoot.tsx | 29 +- 13 files changed, 653 insertions(+), 267 deletions(-) create mode 100644 packages/graph-explorer/src/components/AlertDialog.tsx create mode 100644 packages/graph-explorer/src/components/Group.tsx create mode 100644 packages/graph-explorer/src/components/LabelledSetting.tsx create mode 100644 packages/graph-explorer/src/components/SettingsPage.tsx delete mode 100644 packages/graph-explorer/src/components/SettingsSection.tsx diff --git a/packages/graph-explorer/src/components/AlertDialog.tsx b/packages/graph-explorer/src/components/AlertDialog.tsx new file mode 100644 index 000000000..01928401a --- /dev/null +++ b/packages/graph-explorer/src/components/AlertDialog.tsx @@ -0,0 +1,202 @@ +"use client"; + +import { AlertDialog as AlertDialogPrimitive } from "radix-ui"; +import * as React from "react"; + +import { cn } from "@/utils"; + +import { Button } from "./Button"; + +function AlertDialog({ + ...props +}: React.ComponentProps) { + return ; +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogContent({ + className, + size = "default", + ...props +}: React.ComponentProps & { + size?: "default" | "sm"; +}) { + return ( + + +
+ +
+
+ ); +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDialogMedia({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogAction({ + className, + variant = "outline", + size = "default", + ...props +}: React.ComponentProps & + Pick, "variant" | "size">) { + return ( + + ); +} + +function AlertDialogCancel({ + className, + variant = "outline", + size = "default", + ...props +}: React.ComponentProps & + Pick, "variant" | "size">) { + return ( + + ); +} + +export { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogMedia, + AlertDialogOverlay, + AlertDialogPortal, + AlertDialogTitle, + AlertDialogTrigger, +}; diff --git a/packages/graph-explorer/src/components/Button/Button.tsx b/packages/graph-explorer/src/components/Button/Button.tsx index 17cddeaf4..dca99299a 100644 --- a/packages/graph-explorer/src/components/Button/Button.tsx +++ b/packages/graph-explorer/src/components/Button/Button.tsx @@ -17,6 +17,8 @@ const buttonStyles = cva({ "text-primary-foreground hover:bg-primary-subtle data-open:bg-primary-subtle", outline: "text-text-primary border-input hover:bg-muted data-open:border-primary-main border bg-transparent shadow-xs", + "primary-danger": + "bg-danger hover:bg-danger-hover data-open:bg-danger-hover text-white", danger: "bg-danger-subtle text-danger hover:bg-danger-subtle-hover data-open:bg-danger-subtle-hover", "danger-ghost": diff --git a/packages/graph-explorer/src/components/Group.tsx b/packages/graph-explorer/src/components/Group.tsx new file mode 100644 index 000000000..e8edb8d50 --- /dev/null +++ b/packages/graph-explorer/src/components/Group.tsx @@ -0,0 +1,89 @@ +import { cva, type VariantProps } from "cva"; + +import { cn } from "@/utils"; + +const groupVariants = cva({ + base: "group/group divide-y rounded-xl border", + variants: { + variant: { + default: "border-border divide-border", + danger: "border-danger-foreground/25 divide-danger-foreground/25", + success: "border-success-foreground/25 divide-success-foreground/25", + warning: "border-warning-foreground/25 divide-warning-foreground/25", + }, + }, + defaultVariants: { + variant: "default", + }, +}); + +function Group({ + className, + variant = "default", + size = "default", + ...props +}: React.ComponentProps<"div"> & + VariantProps & { size?: "default" | "small" }) { + return ( +
+ ); +} + +function GroupHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function GroupMedia({ className, ...props }: React.ComponentProps<"div">) { + return ( +
svg]:size-4", className)} + {...props} + /> + ); +} + +function GroupTitle({ + className, + children, + ...props +}: React.ComponentProps<"h2">) { + return ( +

+ {children} +

+ ); +} + +function GroupItem({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +export { Group, GroupHeader, GroupMedia, GroupTitle, GroupItem }; +export type { VariantProps }; diff --git a/packages/graph-explorer/src/components/LabelledSetting.tsx b/packages/graph-explorer/src/components/LabelledSetting.tsx new file mode 100644 index 000000000..a7aaf0c86 --- /dev/null +++ b/packages/graph-explorer/src/components/LabelledSetting.tsx @@ -0,0 +1,31 @@ +import type { PropsWithChildren } from "react"; + +export function LabelledSetting({ + label, + description, + htmlFor, + children, +}: PropsWithChildren<{ + label: string; + description?: string; + htmlFor?: string; +}>) { + return ( +
+ + {children} +
+ ); +} diff --git a/packages/graph-explorer/src/components/NavBar.tsx b/packages/graph-explorer/src/components/NavBar.tsx index f552aff06..af9ffb627 100644 --- a/packages/graph-explorer/src/components/NavBar.tsx +++ b/packages/graph-explorer/src/components/NavBar.tsx @@ -56,7 +56,10 @@ export function NavBarActions({ ); } -function NavBarLogo({ className, ...rest }: ComponentPropsWithRef<"div">) { +export function NavBarLogo({ + className, + ...rest +}: ComponentPropsWithRef<"div">) { return (
) { + return
; +} + +export function SettingsPageHeader({ + className, + ...props +}: React.ComponentPropsWithRef<"div">) { + return ( +
+ ); +} + +export function SettingsPageIcon({ + className, + ...props +}: React.ComponentPropsWithRef<"div">) { + return ( +
+ ); +} + +export function SettingsPageTitle({ + className, + children, + ...props +}: React.ComponentPropsWithRef<"h1">) { + return ( +

+ {children} +

+ ); +} + +export function SettingsPageDescription({ + className, + ...props +}: React.ComponentPropsWithRef<"p">) { + return ( +

+ ); +} diff --git a/packages/graph-explorer/src/components/SettingsSection.tsx b/packages/graph-explorer/src/components/SettingsSection.tsx deleted file mode 100644 index 09fd42cc9..000000000 --- a/packages/graph-explorer/src/components/SettingsSection.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import type { - ComponentProps, - ComponentPropsWithoutRef, - PropsWithChildren, -} from "react"; - -import { cn } from "@/utils"; - -import { Switch } from "./Switch"; - -/** Provides a default gap between section elements */ -export function SettingsSection({ - className, - ...props -}: ComponentProps<"section">) { - return ( -

- ); -} - -export function SettingsSectionHeader({ - className, - ...props -}: ComponentProps<"header">) { - return
; -} - -export function SettingsSectionTitle({ - className, - children, - ...props -}: ComponentProps<"h2">) { - return ( -

- {children} -

- ); -} - -export function SettingsSectionDescription({ - className, - ...props -}: ComponentProps<"div">) { - return ( -
- ); -} - -/** Contains a list of `SettingsSection` elements and provides a gap between them */ -export function SettingsSectionContainer({ - className, - ...props -}: ComponentProps<"div">) { - return
; -} - -export function ToggleSetting({ - label, - description, - ...props -}: ComponentPropsWithoutRef & { - label: string; - description?: string; -}) { - return ( - - ); -} - -export function LabelledSetting({ - label, - description, - children, -}: PropsWithChildren<{ - label: string; - description?: string; -}>) { - return ( -
-
-

- {label} -

- {description ? ( -

- {description} -

- ) : null} -
- {children} -
- ); -} diff --git a/packages/graph-explorer/src/components/index.ts b/packages/graph-explorer/src/components/index.ts index 3dc6d0b41..46d05448c 100644 --- a/packages/graph-explorer/src/components/index.ts +++ b/packages/graph-explorer/src/components/index.ts @@ -1,4 +1,5 @@ export * from "./Alert"; +export * from "./AlertDialog"; export * from "./Button"; export * from "./Checkbox"; @@ -32,6 +33,7 @@ export * from "./EmptyState"; export * from "./Field"; export * from "./FileButton"; export * from "./Form"; +export * from "./Group"; export * from "./IconPicker"; @@ -40,6 +42,7 @@ export * from "./numberFormat"; export * from "./icons"; export * from "./Input"; +export * from "./LabelledSetting"; export { default as InputField } from "./InputField"; export * from "./InputField"; @@ -75,7 +78,7 @@ export { default as SelectField } from "./SelectField"; export * from "./SelectField"; export * from "./Separator"; -export * from "./SettingsSection"; +export * from "./SettingsPage"; export * from "./SidebarTabs"; diff --git a/packages/graph-explorer/src/index.css b/packages/graph-explorer/src/index.css index 980f7519b..ec6ddbd63 100644 --- a/packages/graph-explorer/src/index.css +++ b/packages/graph-explorer/src/index.css @@ -3,13 +3,13 @@ @config "../tailwind.config.ts"; -/* +/* * Use `dark` class name to indicate dark mode. - * + * * We will need to slowly build up dark mode support component by component, so * this is a way to force dark mode to be off, unless we explicitly set `dark` * in the classes in the root component. - * + * * https://github.com/aws/graph-explorer/issues/645 */ @custom-variant dark (&:where(.dark, .dark *)); @@ -39,10 +39,10 @@ /** * The brand color serves as the primary accent color for Graph Explorer. - * + * * It is a custom blue color that is between Tailwind's sky and blue colors, - * with slightly less vibrancy. - * + * with slightly less vibrancy. + * * Generated using: * https://www.tints.dev/palette/v1:YnJhbmR8MTM3Q0M3fDYwMHxwfDB8MHwwfDEwMHxt */ @@ -64,7 +64,7 @@ --color-background: var(--color-white); --color-background-alt: var(--color-gray-50); - --color-muted-foreground: var(--color-gray-600); + --color-muted-foreground: var(--color-gray-500); --color-muted: var(--color-gray-100); /* Used for highlights and selections */ diff --git a/packages/graph-explorer/src/modules/SchemaGraph/SchemaGraphToolbar.tsx b/packages/graph-explorer/src/modules/SchemaGraph/SchemaGraphToolbar.tsx index 1725dd32a..b38665dcf 100644 --- a/packages/graph-explorer/src/modules/SchemaGraph/SchemaGraphToolbar.tsx +++ b/packages/graph-explorer/src/modules/SchemaGraph/SchemaGraphToolbar.tsx @@ -17,7 +17,7 @@ import { ZoomToFitButton, } from "@/components/Graph"; import { useSchemaSync } from "@/hooks/useSchemaSync"; -import { logger } from "@/utils"; +import { ASCII, logger } from "@/utils"; import { schemaGraphLayoutAtom } from "./SchemaGraph"; @@ -69,7 +69,7 @@ function SchemaRefreshButton() { - Refresh Schema + Refresh{ASCII.NBSP}Schema ); } diff --git a/packages/graph-explorer/src/routes/Settings/SettingsAbout.tsx b/packages/graph-explorer/src/routes/Settings/SettingsAbout.tsx index 3515a8c4e..2716f1d46 100644 --- a/packages/graph-explorer/src/routes/Settings/SettingsAbout.tsx +++ b/packages/graph-explorer/src/routes/Settings/SettingsAbout.tsx @@ -1,35 +1,72 @@ +import { InfoIcon } from "lucide-react"; + import { Button, + Group, + GroupHeader, + GroupItem, + GroupTitle, LabelledSetting, - PageHeading, + NavBarLogo, SendIcon, - SettingsSection, - SettingsSectionContainer, + SettingsPageDescription, + SettingsPageHeader, + SettingsPageIcon, + SettingsPageTitle, + SettingsPage, } from "@/components"; import { env } from "@/utils"; import { LABELS } from "@/utils/constants"; export default function SettingsAbout() { return ( - - {LABELS.APP_NAME} - - - - - - - + + + + + + About + + Version details and feedback form. + + + + + +
+ +
+
+

+ {LABELS.APP_NAME} +

+

+ App version:{" "} + + {__GRAPH_EXP_VERSION__} + +

+
+
+
+ + + Feedback + + + + + + + +
); } diff --git a/packages/graph-explorer/src/routes/Settings/SettingsGeneral.tsx b/packages/graph-explorer/src/routes/Settings/SettingsGeneral.tsx index 451011b7a..57918ea0a 100644 --- a/packages/graph-explorer/src/routes/Settings/SettingsGeneral.tsx +++ b/packages/graph-explorer/src/routes/Settings/SettingsGeneral.tsx @@ -1,20 +1,24 @@ import { useAtom } from "jotai"; import localforage from "localforage"; -import { SaveAllIcon } from "lucide-react"; +import { CogIcon, SaveAllIcon } from "lucide-react"; import { AnimatePresence, motion } from "motion/react"; import { Button, - FormItem, + Group, + GroupHeader, + GroupItem, + GroupTitle, ImportantBlock, Input, - Label, LabelledSetting, NotInProduction, - PageHeading, - SettingsSection, - SettingsSectionContainer, - ToggleSetting, + SettingsPageDescription, + SettingsPageHeader, + SettingsPageIcon, + SettingsPageTitle, + SettingsPage, + Switch, } from "@/components"; import { addRemoveAnimationProps } from "@/components/CommonAnimationProps"; import { @@ -49,109 +53,165 @@ export default function SettingsGeneral() { ] = useAtom(defaultNeighborExpansionLimitEnabledAtom); return ( - - General Settings - - { - setDefaultNeighborExpansionLimitEnabled(Boolean(isSelected)); - }} - /> + + + + + + General Settings + + Control how the canvas behaves, manage your configuration data, and + configure diagnostic settings. + + + + + + Canvas Behavior + + + + { + setDefaultNeighborExpansionLimitEnabled(Boolean(isSelected)); + }} + /> + + + {defaultNeighborExpansionLimitEnabled ? ( - - - - setDefaultNeighborExpansionLimit( - parseInt(e.target.value) ?? 0, - ) - } - min={0} - /> - + + + + setDefaultNeighborExpansionLimit( + parseInt(e.target.value) ?? 0, + ) + } + min={0} + /> + + ) : null} + - - - - - - - -

- Loading configuration data will overwrite all of your current data - with the data within the configuration file. -

-
+ + + + + + + + +

+ Loading configuration data will overwrite all of your current data + with the data within the configuration file. +

+
+
+ - { - setDiagnosticLogging(Boolean(isSelected)); - }} - label="Diagnostic logging" - description="Enables verbose logging to the browser console for troubleshooting." - /> + + + Diagnostics + + + + { + setDiagnosticLogging(Boolean(isSelected)); + }} + /> + + + + + { + setAllowLoggingDbQuery(Boolean(isSelected)); + }} + /> + + +

+ This will not log any data returned by the database + queries. However, the node & edge labels, ID values, and any value + filters will be present in the queries. +

+
+
- { - setAllowLoggingDbQuery(Boolean(isSelected)); - }} - label="Enable database query logging on proxy server" - description="Logs the generated database queries to the servers logger. If you have encountered an issue this might be helpful with diagnosing the root cause." - /> - -

- This will not log any data returned by the database queries. - However, the node & edge labels, ID values, and any value filters - will be present in the queries. -

-
- { - setIsDebugOptionsEnabled(Boolean(isSelected)); - }} - label="Show debug actions" - description="Shows debug actions in various places around the app such as buttons to delete the schema or reset the last sync time." - /> + + + { + setIsDebugOptionsEnabled(Boolean(isSelected)); + }} + /> + + -
-
+ + ); } diff --git a/packages/graph-explorer/src/routes/Settings/SettingsRoot.tsx b/packages/graph-explorer/src/routes/Settings/SettingsRoot.tsx index 991c61272..31fa01c3b 100644 --- a/packages/graph-explorer/src/routes/Settings/SettingsRoot.tsx +++ b/packages/graph-explorer/src/routes/Settings/SettingsRoot.tsx @@ -1,8 +1,8 @@ +import { CogIcon, InfoIcon } from "lucide-react"; import { type PropsWithChildren, Suspense } from "react"; import { NavLink, Outlet, type To } from "react-router"; import { - ForwardIcon, NavBar, NavBarContent, NavBarTitle, @@ -32,12 +32,12 @@ export default function SettingsRoot() { - + - + }> @@ -51,13 +51,22 @@ export default function SettingsRoot() { function SideBar() { return ( -