From f70476b1da0996750a733da33423382fe0f7146d Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Tue, 29 Oct 2024 21:55:55 -0700 Subject: [PATCH 01/26] Most of the work to get breadcrumbs in place of top bar pickers --- app/components/TopBarBreadcrumbs.tsx | 257 +++++++++++++++++++++++ app/layouts/ProjectLayout.tsx | 22 +- app/layouts/SiloLayout.tsx | 5 +- app/layouts/SystemLayout.tsx | 12 +- app/ui/styles/components/breadcrumbs.css | 12 ++ app/ui/styles/index.css | 1 + 6 files changed, 281 insertions(+), 28 deletions(-) create mode 100644 app/components/TopBarBreadcrumbs.tsx create mode 100644 app/ui/styles/components/breadcrumbs.css diff --git a/app/components/TopBarBreadcrumbs.tsx b/app/components/TopBarBreadcrumbs.tsx new file mode 100644 index 0000000000..22f7399494 --- /dev/null +++ b/app/components/TopBarBreadcrumbs.tsx @@ -0,0 +1,257 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright Oxide Computer Company + */ +import { Link, useParams } from 'react-router-dom' + +import { Slash } from '~/ui/lib/Slash' +import { pb } from '~/util/path-builder' + +export const TopBarBreadcrumbs = () => { + const firstPathItem = window.location.pathname.split('/')[1] + const secondPathItem = window.location.pathname.split('/')[2] + return ( + + ) +} + +type BreadcrumbProps = { + to: string + text: string + includeSeparator?: boolean +} +export const Breadcrumb = ({ to, text, includeSeparator = true }: BreadcrumbProps) => { + return ( + <> + {includeSeparator && } + + {text} + + + ) +} + +/** If on a Project index or view page, returns appropriate breadcrumb links */ +const ProjectBreadcrumb = () => { + const { project } = useParams() + // check to see if 'projects' is in the url + const projects = window.location.pathname.split('/')[1] === 'projects' + // if 'projects' is not in the URL, return null + if (!projects) { + return null + } + // at this point, we know 'projects' is in the URL + // if project is not defined, return the root projects link + const projectsLink = ( + <> + < + + + ) + if (!project) { + return projectsLink + } + // if project is defined, return the root projects link and the project link, together + return ( + <> + {projectsLink} + + + ) +} + +/** If on an Instance index or view page, returns appropriate breadcrumb links */ +const InstanceBreadcrumb = () => { + const { project, instance } = useParams() + // can't have instances without a project + if (!project) { + return null + } + // check to see if 'instaces' is in the url + const instances = window.location.pathname.split('/')[3] === 'instances' + // if 'instances' is not in the URL, return null + if (!instances) { + return null + } + // at this point, we know 'instances' is in the URL + // if instance is not defined, return the root instances link + const instancesLink = + if (!instance) { + return instancesLink + } + // if instance is defined, return the root instances link and the instance link, together + return ( + <> + {instancesLink} + + + ) +} + +/** If on the Disks index page, returns appropriate breadcrumb link */ +const DisksBreadcrumb = () => { + const { project } = useParams() + // check to see if 'disks' is in the url + const disks = window.location.pathname.split('/')[3] === 'disks' + // if 'disks' is not in the URL, return null + if (!project || !disks) { + return null + } + return +} + +const SnapshotsBreadcrumb = () => { + const { project } = useParams() + // check to see if 'snapshots' is in the url + const snapshots = window.location.pathname.split('/')[3] === 'snapshots' + // if 'snapshots' is not in the URL, return null + if (!project || !snapshots) { + return null + } + return +} + +/** If on the Images index page, returns appropriate breadcrumb link + * Individual images are handled in a sidebar, so we don't need a + * breadcrumb link for individual image pages. + */ +const ImagesBreadcrumb = () => { + const { project } = useParams() + // check to see if 'images' is in the url + const images = window.location.pathname.split('/')[3] === 'images' + // if 'images' is not in the URL, return null + if (!project || !images) { + return null + } + return +} + +const VpcsBreadcrumb = () => { + const { project, vpc } = useParams() + // check to see if 'vpcs' is in the url + const vpcs = window.location.pathname.split('/')[3] === 'vpcs' + // if 'vpcs' is not in the URL, return null + if (!project || !vpcs) { + return null + } + // at this point, we know 'vpcs' is in the URL + // if vpc is not defined, return the root vpcs link + const vpcsLink = + if (!vpc) { + return vpcsLink + } + // if vpc is defined, return the root vpcs link and the vpc link, together + return ( + <> + {vpcsLink} + + + ) +} + +/** If on a Router index or view page, returns appropriate breadcrumb links */ +const VpcRouterBreadcrumb = () => { + const { project, vpc, router } = useParams() + // can't have routers without a vpc + if (!project || !vpc) { + return null + } + // check to see if 'routers' is in the url + const routers = window.location.pathname.split('/')[5] === 'routers' + // if 'routers' is not in the URL, return null + if (!routers) { + return null + } + // at this point, we know 'routers' is in the URL + // if router is not defined, return the root routers link + const routersLink = + if (!router) { + return routersLink + } + // if router is defined, return the root routers link and the router link, together + return ( + <> + {routersLink} + + + ) +} + +const FloatingIpsBreadcrumb = () => { + const { project } = useParams() + // check to see if 'floating-ips' is in the url + const floatingIps = window.location.pathname.split('/')[3] === 'floating-ips' + // if 'floating-ips' is not in the URL, return null + if (!project || !floatingIps) { + return null + } + return +} + +const AccessBreadcrumb = () => { + const { project } = useParams() + // check to see if 'access' is in the url + const access = window.location.pathname.split('/')[3] === 'access' + // if 'access' is not in the URL, return null + if (!project || !access) { + return null + } + return +} + +const SilosBreadcrumb = () => { + const { silo } = useParams() + // check to see if 'silos' is in the url + const silos = window.location.pathname.split('/')[2] === 'silos' + // if 'silos' is not in the URL, return null + if (!silos) { + return null + } + // at this point, we know 'silos' is in the URL + // if silo is not defined, return the root silos link + const silosLink = + if (!silo) { + return silosLink + } + // if silo is defined, return the root silos link and the silo link, together + return ( + <> + {silosLink} + + + ) +} diff --git a/app/layouts/ProjectLayout.tsx b/app/layouts/ProjectLayout.tsx index 5c186c213a..daedb9843d 100644 --- a/app/layouts/ProjectLayout.tsx +++ b/app/layouts/ProjectLayout.tsx @@ -6,12 +6,7 @@ * Copyright Oxide Computer Company */ import { useMemo, type ReactElement } from 'react' -import { - useLocation, - useNavigate, - useParams, - type LoaderFunctionArgs, -} from 'react-router-dom' +import { useLocation, useNavigate, type LoaderFunctionArgs } from 'react-router-dom' import { apiQueryClient, usePrefetchedApiQuery } from '@oxide/api' import { @@ -26,13 +21,8 @@ import { } from '@oxide/design-system/icons/react' import { TopBar } from '~/components/TopBar' -import { - InstancePicker, - ProjectPicker, - SiloSystemPicker, - VpcPicker, - VpcRouterPicker, -} from '~/components/TopBarPicker' +import { TopBarBreadcrumbs } from '~/components/TopBarBreadcrumbs' +import { SiloSystemPicker } from '~/components/TopBarPicker' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { useQuickActions } from '~/hooks/use-quick-actions' import { Divider } from '~/ui/lib/Divider' @@ -61,7 +51,6 @@ export function ProjectLayout({ overrideContentPane }: ProjectLayoutProps) { const projectSelector = useProjectSelector() const { data: project } = usePrefetchedApiQuery('projectView', { path: projectSelector }) - const { instance, router, vpc } = useParams() const { pathname } = useLocation() useQuickActions( useMemo( @@ -90,10 +79,7 @@ export function ProjectLayout({ overrideContentPane }: ProjectLayoutProps) { - - {instance && } - {vpc && } - {router && } + diff --git a/app/layouts/SiloLayout.tsx b/app/layouts/SiloLayout.tsx index bb8e946c16..142ed9bba6 100644 --- a/app/layouts/SiloLayout.tsx +++ b/app/layouts/SiloLayout.tsx @@ -17,7 +17,8 @@ import { import { DocsLinkItem, NavLinkItem, Sidebar } from '~/components/Sidebar' import { TopBar } from '~/components/TopBar' -import { ProjectPicker, SiloSystemPicker } from '~/components/TopBarPicker' +import { TopBarBreadcrumbs } from '~/components/TopBarBreadcrumbs' +import { SiloSystemPicker } from '~/components/TopBarPicker' import { useQuickActions } from '~/hooks/use-quick-actions' import { Divider } from '~/ui/lib/Divider' import { pb } from '~/util/path-builder' @@ -54,7 +55,7 @@ export function SiloLayout() { - + diff --git a/app/layouts/SystemLayout.tsx b/app/layouts/SystemLayout.tsx index 22aeda4281..d805f2005b 100644 --- a/app/layouts/SystemLayout.tsx +++ b/app/layouts/SystemLayout.tsx @@ -19,12 +19,8 @@ import { import { trigger404 } from '~/components/ErrorBoundary' import { DocsLinkItem, NavLinkItem, Sidebar } from '~/components/Sidebar' import { TopBar } from '~/components/TopBar' -import { - IpPoolPicker, - SiloPicker, - SiloSystemPicker, - SledPicker, -} from '~/components/TopBarPicker' +import { TopBarBreadcrumbs } from '~/components/TopBarBreadcrumbs' +import { IpPoolPicker, SiloSystemPicker, SledPicker } from '~/components/TopBarPicker' import { useQuickActions } from '~/hooks/use-quick-actions' import { Divider } from '~/ui/lib/Divider' import { pb } from '~/util/path-builder' @@ -60,7 +56,7 @@ export function SystemLayout() { // robust way of doing this would be to make a separate layout for the // silo-specific routes in the route config, but it's overkill considering // this is a one-liner. Switch to that approach at the first sign of trouble. - const { silo, pool, sledId } = useParams() + const { pool, sledId } = useParams() const navigate = useNavigate() const { pathname } = useLocation() @@ -95,7 +91,7 @@ export function SystemLayout() { - {silo && } + {pool && } {sledId && } diff --git a/app/ui/styles/components/breadcrumbs.css b/app/ui/styles/components/breadcrumbs.css new file mode 100644 index 0000000000..9b15191664 --- /dev/null +++ b/app/ui/styles/components/breadcrumbs.css @@ -0,0 +1,12 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright Oxide Computer Company + */ + +/* the last .ox-breadcrumb in the list should have text-default applied */ +.ox-breadcrumb:last-child { + @apply text-default; +} \ No newline at end of file diff --git a/app/ui/styles/index.css b/app/ui/styles/index.css index 5746fa8af6..f49b824cfe 100644 --- a/app/ui/styles/index.css +++ b/app/ui/styles/index.css @@ -24,6 +24,7 @@ @import 'simplebar-react/dist/simplebar.min.css'; @import './fonts.css'; +@import './components/breadcrumbs.css'; @import './components/button.css'; @import './components/menu-button.css'; @import './components/menu-list.css'; From db213c12ba42b4f7d0f7ae55b0c46878040dad41 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 30 Oct 2024 04:57:15 +0000 Subject: [PATCH 02/26] Bot commit: format with prettier --- app/ui/styles/components/breadcrumbs.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ui/styles/components/breadcrumbs.css b/app/ui/styles/components/breadcrumbs.css index 9b15191664..f884571182 100644 --- a/app/ui/styles/components/breadcrumbs.css +++ b/app/ui/styles/components/breadcrumbs.css @@ -9,4 +9,4 @@ /* the last .ox-breadcrumb in the list should have text-default applied */ .ox-breadcrumb:last-child { @apply text-default; -} \ No newline at end of file +} From c6bd472b9d8d63e7b0310589f6dc271ad529f4cd Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Wed, 30 Oct 2024 10:07:38 -0700 Subject: [PATCH 03/26] Refactor; add System page breadcrumbs --- app/components/TopBarBreadcrumbs.tsx | 269 +++++++++------------------ app/components/TopBarPicker.tsx | 175 +---------------- app/layouts/SystemLayout.tsx | 7 +- 3 files changed, 96 insertions(+), 355 deletions(-) diff --git a/app/components/TopBarBreadcrumbs.tsx b/app/components/TopBarBreadcrumbs.tsx index 22f7399494..0a97d2eab7 100644 --- a/app/components/TopBarBreadcrumbs.tsx +++ b/app/components/TopBarBreadcrumbs.tsx @@ -11,47 +11,93 @@ import { Slash } from '~/ui/lib/Slash' import { pb } from '~/util/path-builder' export const TopBarBreadcrumbs = () => { - const firstPathItem = window.location.pathname.split('/')[1] - const secondPathItem = window.location.pathname.split('/')[2] + const [, firstPathItem, secondPathItem, thirdPathItem, , fifthPathItem] = + window.location.pathname.split('/') + const { project } = useParams() return ( ) } type BreadcrumbProps = { to: string - text: string + label: string includeSeparator?: boolean } -export const Breadcrumb = ({ to, text, includeSeparator = true }: BreadcrumbProps) => { +export const Breadcrumb = ({ to, label, includeSeparator = true }: BreadcrumbProps) => { return ( <> {includeSeparator && } @@ -59,199 +105,70 @@ export const Breadcrumb = ({ to, text, includeSeparator = true }: BreadcrumbProp to={to} className="ox-breadcrumb whitespace-nowrap text-sans-sm text-secondary hover:text-default" > - {text} + {label} ) } -/** If on a Project index or view page, returns appropriate breadcrumb links */ -const ProjectBreadcrumb = () => { - const { project } = useParams() - // check to see if 'projects' is in the url - const projects = window.location.pathname.split('/')[1] === 'projects' - // if 'projects' is not in the URL, return null - if (!projects) { - return null - } - // at this point, we know 'projects' is in the URL - // if project is not defined, return the root projects link - const projectsLink = ( +const InstanceBreadcrumb = ({ project }: { project: string }) => { + const { instance } = useParams() + return ( <> - < - + + {instance && } ) - if (!project) { - return projectsLink - } - // if project is defined, return the root projects link and the project link, together +} + +const VpcsBreadcrumb = ({ project }: { project: string }) => { + const { vpc } = useParams() return ( <> - {projectsLink} - + + {vpc && } ) } -/** If on an Instance index or view page, returns appropriate breadcrumb links */ -const InstanceBreadcrumb = () => { - const { project, instance } = useParams() - // can't have instances without a project - if (!project) { - return null - } - // check to see if 'instaces' is in the url - const instances = window.location.pathname.split('/')[3] === 'instances' - // if 'instances' is not in the URL, return null - if (!instances) { - return null - } - // at this point, we know 'instances' is in the URL - // if instance is not defined, return the root instances link - const instancesLink = - if (!instance) { - return instancesLink - } - // if instance is defined, return the root instances link and the instance link, together +const VpcRouterBreadcrumb = ({ project }: { project: string }) => { + const { vpc, router } = useParams() return ( <> - {instancesLink} - + {vpc && } + {vpc && router && ( + + )} ) } -/** If on the Disks index page, returns appropriate breadcrumb link */ -const DisksBreadcrumb = () => { - const { project } = useParams() - // check to see if 'disks' is in the url - const disks = window.location.pathname.split('/')[3] === 'disks' - // if 'disks' is not in the URL, return null - if (!project || !disks) { - return null - } - return -} - -const SnapshotsBreadcrumb = () => { - const { project } = useParams() - // check to see if 'snapshots' is in the url - const snapshots = window.location.pathname.split('/')[3] === 'snapshots' - // if 'snapshots' is not in the URL, return null - if (!project || !snapshots) { - return null - } - return -} - -/** If on the Images index page, returns appropriate breadcrumb link - * Individual images are handled in a sidebar, so we don't need a - * breadcrumb link for individual image pages. - */ -const ImagesBreadcrumb = () => { - const { project } = useParams() - // check to see if 'images' is in the url - const images = window.location.pathname.split('/')[3] === 'images' - // if 'images' is not in the URL, return null - if (!project || !images) { - return null - } - return -} - -const VpcsBreadcrumb = () => { - const { project, vpc } = useParams() - // check to see if 'vpcs' is in the url - const vpcs = window.location.pathname.split('/')[3] === 'vpcs' - // if 'vpcs' is not in the URL, return null - if (!project || !vpcs) { - return null - } - // at this point, we know 'vpcs' is in the URL - // if vpc is not defined, return the root vpcs link - const vpcsLink = - if (!vpc) { - return vpcsLink - } - // if vpc is defined, return the root vpcs link and the vpc link, together +const SilosBreadcrumb = () => { + const { silo } = useParams() return ( <> - {vpcsLink} - + + {silo && } ) } -/** If on a Router index or view page, returns appropriate breadcrumb links */ -const VpcRouterBreadcrumb = () => { - const { project, vpc, router } = useParams() - // can't have routers without a vpc - if (!project || !vpc) { - return null - } - // check to see if 'routers' is in the url - const routers = window.location.pathname.split('/')[5] === 'routers' - // if 'routers' is not in the URL, return null - if (!routers) { - return null - } - // at this point, we know 'routers' is in the URL - // if router is not defined, return the root routers link - const routersLink = - if (!router) { - return routersLink - } - // if router is defined, return the root routers link and the router link, together +const SystemSledInventoryBreadcrumb = () => { + const { sledId } = useParams() return ( <> - {routersLink} - + + {sledId && } ) } -const FloatingIpsBreadcrumb = () => { - const { project } = useParams() - // check to see if 'floating-ips' is in the url - const floatingIps = window.location.pathname.split('/')[3] === 'floating-ips' - // if 'floating-ips' is not in the URL, return null - if (!project || !floatingIps) { - return null - } - return -} - -const AccessBreadcrumb = () => { - const { project } = useParams() - // check to see if 'access' is in the url - const access = window.location.pathname.split('/')[3] === 'access' - // if 'access' is not in the URL, return null - if (!project || !access) { - return null - } - return -} - -const SilosBreadcrumb = () => { - const { silo } = useParams() - // check to see if 'silos' is in the url - const silos = window.location.pathname.split('/')[2] === 'silos' - // if 'silos' is not in the URL, return null - if (!silos) { - return null - } - // at this point, we know 'silos' is in the URL - // if silo is not defined, return the root silos link - const silosLink = - if (!silo) { - return silosLink - } - // if silo is defined, return the root silos link and the silo link, together +const SystemIpPoolsBreadcrumb = () => { + const { pool } = useParams() return ( <> - {silosLink} - + + {pool && } ) } diff --git a/app/components/TopBarPicker.tsx b/app/components/TopBarPicker.tsx index 5ae3432948..077090cd4d 100644 --- a/app/components/TopBarPicker.tsx +++ b/app/components/TopBarPicker.tsx @@ -8,23 +8,9 @@ import cn from 'classnames' import { Link } from 'react-router-dom' -import { useApiQuery, type Project } from '@oxide/api' -import { - Folder16Icon, - SelectArrows6Icon, - Success12Icon, -} from '@oxide/design-system/icons/react' +import { SelectArrows6Icon, Success12Icon } from '@oxide/design-system/icons/react' -import { - useInstanceSelector, - useIpPoolSelector, - useSiloSelector, - useSledParams, - useVpcRouterSelector, - useVpcSelector, -} from '~/hooks/use-params' import { useCurrentUser } from '~/layouts/AuthenticatedLayout' -import { PAGE_SIZE } from '~/table/QueryTable' import { buttonStyle } from '~/ui/lib/Button' import * as DropdownMenu from '~/ui/lib/DropdownMenu' import { Identicon } from '~/ui/lib/Identicon' @@ -207,162 +193,3 @@ export function SiloSystemPicker({ value }: { value: 'silo' | 'system' }) { /> ) } - -/** Used when drilling down into a silo from the System view. */ -export function SiloPicker() { - // picker only shows up when a silo is in scope - const { silo: siloName } = useSiloSelector() - const { data } = useApiQuery('siloList', { query: { limit: PAGE_SIZE } }) - const items = (data?.items || []).map((silo) => ({ - label: silo.name, - to: pb.silo({ silo: silo.name }), - })) - - return ( - } - current={siloName} - items={items} - noItemsText="No silos found" - /> - ) -} - -/** Used when drilling down into a pool from the System/Networking view. */ -export function IpPoolPicker() { - // picker only shows up when a pool is in scope - const { pool: poolName } = useIpPoolSelector() - const { data } = useApiQuery('ipPoolList', { query: { limit: PAGE_SIZE } }) - const items = (data?.items || []).map((pool) => ({ - label: pool.name, - to: pb.ipPool({ pool: pool.name }), - })) - - return ( - - ) -} - -/** Used when drilling down into a VPC from the Silo view. */ -export function VpcPicker() { - // picker only shows up when a VPC is in scope - const { project, vpc } = useVpcSelector() - const { data } = useApiQuery('vpcList', { query: { project, limit: PAGE_SIZE } }) - const items = (data?.items || []).map((v) => ({ - label: v.name, - to: pb.vpc({ project, vpc: v.name }), - })) - - return ( - - ) -} - -/** Used when drilling down into a VPC Router from the Silo view. */ -export function VpcRouterPicker() { - // picker only shows up when a router is in scope - const { project, vpc, router } = useVpcRouterSelector() - const { data } = useApiQuery('vpcRouterList', { - query: { project, vpc, limit: PAGE_SIZE }, - }) - const items = (data?.items || []).map((r) => ({ - label: r.name, - to: pb.vpcRouter({ vpc, project, router: r.name }), - })) - - return ( - - ) -} - -const NoProjectLogo = () => ( -
- -
-) - -export function ProjectPicker({ project }: { project?: Project }) { - const { data: projects } = useApiQuery('projectList', { query: { limit: 200 } }) - const items = (projects?.items || []).map(({ name }) => ({ - label: name, - to: pb.project({ project: name }), - })) - - return ( - } - category="Project" - current={project?.name} - to={project ? pb.project({ project: project.name }) : undefined} - items={items} - noItemsText="No projects found" - /> - ) -} - -export function InstancePicker() { - // picker only shows up when an instance is in scope - const instanceSelector = useInstanceSelector() - const { project, instance } = instanceSelector - const { data: instances } = useApiQuery('instanceList', { - query: { project, limit: PAGE_SIZE }, - }) - const items = (instances?.items || []).map(({ name }) => ({ - label: name, - to: pb.instance({ project, instance: name }), - })) - return ( - - ) -} - -export function SledPicker() { - // picker only shows up when a sled is in scope - const { sledId } = useSledParams() - const { data: sleds } = useApiQuery('sledList', { - query: { limit: PAGE_SIZE }, - }) - const items = (sleds?.items || []).map(({ id }) => ({ - label: id, - to: pb.sled({ sledId: id }), - })) - return ( - - ) -} diff --git a/app/layouts/SystemLayout.tsx b/app/layouts/SystemLayout.tsx index d805f2005b..0d3c080e04 100644 --- a/app/layouts/SystemLayout.tsx +++ b/app/layouts/SystemLayout.tsx @@ -6,7 +6,7 @@ * Copyright Oxide Computer Company */ import { useMemo } from 'react' -import { useLocation, useNavigate, useParams } from 'react-router-dom' +import { useLocation, useNavigate } from 'react-router-dom' import { apiQueryClient } from '@oxide/api' import { @@ -20,7 +20,7 @@ import { trigger404 } from '~/components/ErrorBoundary' import { DocsLinkItem, NavLinkItem, Sidebar } from '~/components/Sidebar' import { TopBar } from '~/components/TopBar' import { TopBarBreadcrumbs } from '~/components/TopBarBreadcrumbs' -import { IpPoolPicker, SiloSystemPicker, SledPicker } from '~/components/TopBarPicker' +import { SiloSystemPicker } from '~/components/TopBarPicker' import { useQuickActions } from '~/hooks/use-quick-actions' import { Divider } from '~/ui/lib/Divider' import { pb } from '~/util/path-builder' @@ -56,7 +56,6 @@ export function SystemLayout() { // robust way of doing this would be to make a separate layout for the // silo-specific routes in the route config, but it's overkill considering // this is a one-liner. Switch to that approach at the first sign of trouble. - const { pool, sledId } = useParams() const navigate = useNavigate() const { pathname } = useLocation() @@ -92,8 +91,6 @@ export function SystemLayout() { - {pool && } - {sledId && } From 98025ff8ab4225cddc4c42793475a2b1427d3df2 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Wed, 30 Oct 2024 10:37:35 -0700 Subject: [PATCH 04/26] proper arrow; spacing tweaks --- app/components/TopBarBreadcrumbs.tsx | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/components/TopBarBreadcrumbs.tsx b/app/components/TopBarBreadcrumbs.tsx index 0a97d2eab7..54c13bddec 100644 --- a/app/components/TopBarBreadcrumbs.tsx +++ b/app/components/TopBarBreadcrumbs.tsx @@ -7,6 +7,8 @@ */ import { Link, useParams } from 'react-router-dom' +import { PrevArrow12Icon } from '@oxide/design-system/icons/react' + import { Slash } from '~/ui/lib/Slash' import { pb } from '~/util/path-builder' @@ -16,7 +18,7 @@ export const TopBarBreadcrumbs = () => { const { project } = useParams() return (