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 ( -