diff --git a/Changelog.md b/Changelog.md index b028921e0..476604382 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,8 @@ ## Upcoming +- **Improved** style for the sidebar buttons + [#651](https://github.com/aws/graph-explorer/pull/651) - **Fixed** Docker image containing more files than necessary. ([#613](https://github.com/aws/graph-explorer/pull/613)) - **Fixed** conflict when a node has a property named "id" that prevented diff --git a/packages/graph-explorer/package.json b/packages/graph-explorer/package.json index 94cc19933..35bc7b11a 100644 --- a/packages/graph-explorer/package.json +++ b/packages/graph-explorer/package.json @@ -25,6 +25,7 @@ "@mantine/dates": "^7.13.2", "@mantine/emotion": "^7.13.2", "@mantine/hooks": "^7.13.2", + "@radix-ui/react-toggle": "^1.1.0", "@react-aria/button": "3.9.8", "@react-aria/checkbox": "3.14.6", "@react-aria/combobox": "3.10.3", diff --git a/packages/graph-explorer/src/components/Sidebar/Sidebar.model.ts b/packages/graph-explorer/src/components/Sidebar/Sidebar.model.ts deleted file mode 100644 index 285cedc48..000000000 --- a/packages/graph-explorer/src/components/Sidebar/Sidebar.model.ts +++ /dev/null @@ -1,14 +0,0 @@ -type BaseSidebarTheme = { - button: { - color: string; - background: string; - active: { - color: string; - background: string; - }; - }; -}; - -export type SidebarTheme = { - sidebar?: DeepPartial; -}; diff --git a/packages/graph-explorer/src/components/Sidebar/Sidebar.styles.ts b/packages/graph-explorer/src/components/Sidebar/Sidebar.styles.ts deleted file mode 100644 index 90df13476..000000000 --- a/packages/graph-explorer/src/components/Sidebar/Sidebar.styles.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { css } from "@emotion/css"; -import type { ThemeStyleFn } from "@/core"; -import { fade } from "@/core"; -import { SidebarTheme } from "./Sidebar.model"; - -const defaultStyles: ThemeStyleFn = ({ theme }) => { - const { sidebar, spacing, palette } = theme; - return css` - &.sidebar { - padding: ${spacing.base}; - height: 100%; - display: flex; - align-items: center; - flex-direction: column; - background-color: ${fade(palette.primary.light, 0.2)}; - color: ${palette.text.primary}; - - .divider { - width: 60%; - margin: ${spacing["2x"]} auto; - height: 1px; - background: ${palette.divider}; - } - - &.active { - color: ${palette.primary.main}; - } - } - - .sidebar-button { - color: ${sidebar?.button?.color || palette.primary.dark}; - background-color: ${ - sidebar?.button?.background || fade(palette.primary.main, 0.2) - }; - margin: ${spacing.base}; - padding: 0; - - &.active { - color: ${sidebar?.button?.active?.color || palette.primary.dark}; - background-color: ${ - sidebar?.button?.active?.background || - fade(palette.primary.main, 0.6) - }; - } - } - } - `; -}; - -export default defaultStyles; diff --git a/packages/graph-explorer/src/components/Sidebar/Sidebar.tsx b/packages/graph-explorer/src/components/Sidebar/Sidebar.tsx deleted file mode 100644 index 72e8e3c93..000000000 --- a/packages/graph-explorer/src/components/Sidebar/Sidebar.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { cn } from "@/utils"; -import type { ForwardedRef, PropsWithChildren } from "react"; -import { forwardRef } from "react"; -import { useWithTheme } from "@/core"; -import defaultStyles from "./Sidebar.styles"; -import SidebarButton from "./SidebarButton"; - -export type SidebarProps = { - className?: string; -}; - -export type SidebarComposition = { - Button: typeof SidebarButton; -}; - -const Sidebar = ( - { children, className }: PropsWithChildren, - ref?: ForwardedRef -) => { - const stylesWithTheme = useWithTheme(); - - return ( -
- {children} -
- ); -}; - -const T = forwardRef(Sidebar) as unknown as SidebarComposition & - (( - props: PropsWithChildren> & { - ref?: ForwardedRef; - } - ) => ReturnType); - -T.Button = SidebarButton; - -export default T; diff --git a/packages/graph-explorer/src/components/Sidebar/SidebarButton.tsx b/packages/graph-explorer/src/components/Sidebar/SidebarButton.tsx deleted file mode 100644 index 7a767cead..000000000 --- a/packages/graph-explorer/src/components/Sidebar/SidebarButton.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { cn } from "@/utils"; -import type { ReactNode } from "react"; -import IconButton, { IconButtonProps } from "../IconButton"; - -export type NavItem = { - icon: ReactNode; - onPress(): void; - tooltipText?: string; -}; - -export type SidebarButtonProps = { - active?: boolean; -} & Omit; - -const SidebarButton = ({ className, active, ...props }: SidebarButtonProps) => { - return ( - - ); -}; - -export default SidebarButton; diff --git a/packages/graph-explorer/src/components/Sidebar/index.ts b/packages/graph-explorer/src/components/Sidebar/index.ts deleted file mode 100644 index 8b3e19051..000000000 --- a/packages/graph-explorer/src/components/Sidebar/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./Sidebar"; -export type { SidebarComposition, SidebarProps } from "./Sidebar"; diff --git a/packages/graph-explorer/src/components/Workspace/components/SidebarButton.tsx b/packages/graph-explorer/src/components/Workspace/components/SidebarButton.tsx new file mode 100644 index 000000000..16e5c0a26 --- /dev/null +++ b/packages/graph-explorer/src/components/Workspace/components/SidebarButton.tsx @@ -0,0 +1,56 @@ +import * as React from "react"; +import * as TogglePrimitive from "@radix-ui/react-toggle"; +import { Tooltip } from "../../Tooltip"; +import { cn } from "@/utils"; + +const SidebarButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + icon: React.ReactElement; + title: React.ReactNode; + badge?: boolean; + } +>(({ icon, title, badge = false, className, children, ...props }, ref) => { + return ( + + + + {icon} + {children} + {title} + + + + ); +}); + +function Badge({ + children, + value, + ...props +}: React.ComponentPropsWithoutRef<"div"> & { value?: boolean }) { + return ( +
+ {children} + {value ? ( + + ) : null} +
+ ); +} + +SidebarButton.displayName = "SidebarButton"; + +export { SidebarButton }; diff --git a/packages/graph-explorer/src/components/Workspace/components/WorkspaceSideBar.tsx b/packages/graph-explorer/src/components/Workspace/components/WorkspaceSideBar.tsx index 4b94f7271..10f162f31 100644 --- a/packages/graph-explorer/src/components/Workspace/components/WorkspaceSideBar.tsx +++ b/packages/graph-explorer/src/components/Workspace/components/WorkspaceSideBar.tsx @@ -3,8 +3,7 @@ import type { PropsWithChildren, ReactElement } from "react"; import { useMemo } from "react"; import getChildOfType from "@/utils/getChildOfType"; import getChildrenOfType from "@/utils/getChildrenOfType"; -import Sidebar from "@/components/Sidebar"; -import SidebarButton from "@/components/Sidebar/SidebarButton"; +import { SidebarButton } from "@/components/Workspace/components/SidebarButton"; import WorkspaceSideBarContent from "./WorkspaceSideBarContent"; interface WorkspaceSideBarComposition { @@ -43,7 +42,9 @@ const WorkspaceSideBar = ({ direction === "row-reverse" && "flex-row-reverse" )} > - {sidebarActions} +
+ {sidebarActions} +
{sidebarContent} ); diff --git a/packages/graph-explorer/src/components/index.ts b/packages/graph-explorer/src/components/index.ts index af75b8707..44ab1f722 100644 --- a/packages/graph-explorer/src/components/index.ts +++ b/packages/graph-explorer/src/components/index.ts @@ -61,9 +61,6 @@ export * from "./Section"; export { default as Select } from "./Select"; export * from "./Select"; -export { default as Sidebar } from "./Sidebar"; -export * from "./Sidebar"; - export * from "./Tooltip"; export * from "./Typography"; diff --git a/packages/graph-explorer/src/core/ThemeProvider/themes/light.ts b/packages/graph-explorer/src/core/ThemeProvider/themes/light.ts index 570000fed..12a4ac3b5 100644 --- a/packages/graph-explorer/src/core/ThemeProvider/themes/light.ts +++ b/packages/graph-explorer/src/core/ThemeProvider/themes/light.ts @@ -20,9 +20,9 @@ const palette: DeepRequired = { black: "#000", }, primary: { - light: "#64c7ff", - main: "#128ee5", - dark: "#17457b", + light: "hsl(var(--color-brand-300))", + main: "hsl(var(--color-brand-500))", + dark: "hsl(var(--color-brand-800))", contrastText: "#ffffff", }, secondary: { diff --git a/packages/graph-explorer/src/core/ThemeProvider/utils/fade.tsx b/packages/graph-explorer/src/core/ThemeProvider/utils/fade.tsx index 73deb3812..fb03ef582 100644 --- a/packages/graph-explorer/src/core/ThemeProvider/utils/fade.tsx +++ b/packages/graph-explorer/src/core/ThemeProvider/utils/fade.tsx @@ -1,9 +1,3 @@ -import Color from "color"; - -const fade = (color: string | undefined, opacity: number) => { - const colorInstance = new Color(color); - const rgb = colorInstance.rgb().array(); - return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${opacity})`; -}; - -export default fade; +export default function fade(color: string | undefined, opacity: number) { + return `color-mix(in srgb, ${color} ${opacity * 100}%, transparent)`; +} diff --git a/packages/graph-explorer/src/index.css b/packages/graph-explorer/src/index.css index 11db5051d..dc9095994 100644 --- a/packages/graph-explorer/src/index.css +++ b/packages/graph-explorer/src/index.css @@ -26,9 +26,17 @@ --color-background-contrast: var(--color-gray-200); --color-background-contrast-secondary: var(--color-gray-300); - --color-primary-light: 100 199 255; - --color-primary-main: 18 142 229; - --color-primary-dark: 23 69 123; + --color-brand-50: 205deg 87% 95%; + --color-brand-100: 205deg 87% 93%; + --color-brand-200: 205deg 87% 82%; + --color-brand-300: 205deg 95% 71%; + --color-brand-400: 205deg 87% 57%; + --color-brand-500: 205deg 85% 48%; + --color-brand-600: 205deg 87% 39%; + --color-brand-700: 205deg 89% 31%; + --color-brand-800: 205deg 92% 24%; + --color-brand-900: 205deg 95% 12%; + --color-brand-950: 205deg 98% 6%; --color-secondary-light: 255 184 46; --color-secondary-main: 250 133 0; @@ -83,6 +91,16 @@ } } +@layer utilities { + .stack { + display: grid; + } + + .stack > * { + grid-area: 1 / 1 / 2 / 2; + } +} + body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", diff --git a/packages/graph-explorer/src/workspaces/GraphExplorer/GraphExplorer.tsx b/packages/graph-explorer/src/workspaces/GraphExplorer/GraphExplorer.tsx index a9dc19f22..59234dc1f 100644 --- a/packages/graph-explorer/src/workspaces/GraphExplorer/GraphExplorer.tsx +++ b/packages/graph-explorer/src/workspaces/GraphExplorer/GraphExplorer.tsx @@ -274,44 +274,42 @@ const GraphExplorer = () => { } - onPress={toggleSidebar("details")} - active={userLayout.activeSidebarItem === "details"} + onPressedChange={toggleSidebar("details")} + pressed={userLayout.activeSidebarItem === "details"} /> } - onPress={toggleSidebar("filters")} - badge={filteredEntitiesCount} - badgeVariant="undetermined" - badgePlacement="top-right" - active={userLayout.activeSidebarItem === "filters"} + onPressedChange={toggleSidebar("filters")} + badge={filteredEntitiesCount > 0} + pressed={userLayout.activeSidebarItem === "filters"} /> } - onPress={toggleSidebar("expand")} - active={userLayout.activeSidebarItem === "expand"} + onPressedChange={toggleSidebar("expand")} + pressed={userLayout.activeSidebarItem === "expand"} /> } - onPress={toggleSidebar("nodes-styling")} - active={userLayout.activeSidebarItem === "nodes-styling"} + onPressedChange={toggleSidebar("nodes-styling")} + pressed={userLayout.activeSidebarItem === "nodes-styling"} /> } - onPress={toggleSidebar("edges-styling")} - active={userLayout.activeSidebarItem === "edges-styling"} + onPressedChange={toggleSidebar("edges-styling")} + pressed={userLayout.activeSidebarItem === "edges-styling"} /> {hasNamespaces && ( } - onPress={toggleSidebar("namespaces")} - active={userLayout.activeSidebarItem === "namespaces"} + onPressedChange={toggleSidebar("namespaces")} + pressed={userLayout.activeSidebarItem === "namespaces"} /> )} diff --git a/packages/graph-explorer/tailwind.config.ts b/packages/graph-explorer/tailwind.config.ts index 1bb30c497..e21f2a86d 100644 --- a/packages/graph-explorer/tailwind.config.ts +++ b/packages/graph-explorer/tailwind.config.ts @@ -14,6 +14,20 @@ const gray = { 900: "rgb(var(--color-gray-900) / )", }; +const blue = { + 50: "hsl(var(--color-brand-50) / )", + 100: "hsl(var(--color-brand-100) / )", + 200: "hsl(var(--color-brand-200) / )", + 300: "hsl(var(--color-brand-300) / )", + 400: "hsl(var(--color-brand-400) / )", + 500: "hsl(var(--color-brand-500) / )", + 600: "hsl(var(--color-brand-600) / )", + 700: "hsl(var(--color-brand-700) / )", + 800: "hsl(var(--color-brand-800) / )", + 900: "hsl(var(--color-brand-900) / )", + 950: "hsl(var(--color-brand-950) / )", +}; + export default { content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], darkMode: "class", @@ -24,10 +38,11 @@ export default { black, white, gray, + brand: blue, primary: { - light: "rgb(var(--color-primary-light) / )", - main: "rgb(var(--color-primary-main) / )", - dark: "rgb(var(--color-primary-dark) / )", + light: blue[300], + main: blue[500], + dark: blue[800], contrastText: white, }, secondary: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 25390902a..46bce75ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -103,6 +103,9 @@ importers: '@mantine/hooks': specifier: ^7.13.2 version: 7.13.2(react@18.3.1) + '@radix-ui/react-toggle': + specifier: ^1.1.0 + version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@react-aria/button': specifier: 3.9.8 version: 3.9.8(react@18.3.1) @@ -1794,6 +1797,71 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@radix-ui/primitive@1.1.0': + resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} + + '@radix-ui/react-compose-refs@1.1.0': + resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-primitive@2.0.0': + resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.1.0': + resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-toggle@1.1.0': + resolution: {integrity: sha512-gwoxaKZ0oJ4vIgzsfESBuSgJNdc0rv12VhHgcqN0TEJmmZixXG/2XpsLK8kzNWYcnaoRIEEQc0bEi3dIvdUpjw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.0': + resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.1.0': + resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@rc-component/portal@1.1.2': resolution: {integrity: sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==} engines: {node: '>=8.x'} @@ -7561,6 +7629,54 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@radix-ui/primitive@1.1.0': {} + + '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.11)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.11 + + '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.11 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-slot@1.1.0(@types/react@18.3.11)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.11 + + '@radix-ui/react-toggle@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.11 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.11)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.11 + + '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.11)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.11 + '@rc-component/portal@1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.25.7