Rework top bar to not include toolbar
Closes #7679 by creating a definite top bar, and moving the toolbar below it. Still side-steps the E2E test issue by allowing the modeling scene extend under this top bar. I will finally address this in the next step, which is bringing back a proper sidebar that doesn't overlay the modeling scene. I have a fast-follow PR coming that adds visual undo and redo buttons to this top bar, but I wanted to keep them separate.
This commit is contained in:
		
							
								
								
									
										49
									
								
								src/App.tsx
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								src/App.tsx
									
									
									
									
									
								
							@ -65,13 +65,15 @@ import { useModelingContext } from '@src/hooks/useModelingContext'
 | 
			
		||||
import { xStateValueToString } from '@src/lib/xStateValueToString'
 | 
			
		||||
import { getSelectionTypeDisplayText } from '@src/lib/selections'
 | 
			
		||||
import type { StatusBarItemType } from '@src/components/StatusBar/statusBarTypes'
 | 
			
		||||
import { Toolbar } from './Toolbar'
 | 
			
		||||
import { UndoRedoButtons } from './components/UndoRedoButtons'
 | 
			
		||||
 | 
			
		||||
// CYCLIC REF
 | 
			
		||||
sceneInfra.camControls.engineStreamActor = engineStreamActor
 | 
			
		||||
 | 
			
		||||
maybeWriteToDisk()
 | 
			
		||||
  .then(() => {})
 | 
			
		||||
  .catch(() => {})
 | 
			
		||||
  .then(() => { })
 | 
			
		||||
  .catch(() => { })
 | 
			
		||||
 | 
			
		||||
export function App() {
 | 
			
		||||
  const { state: modelingState } = useModelingContext()
 | 
			
		||||
@ -246,15 +248,18 @@ export function App() {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="h-screen flex flex-col overflow-hidden select-none">
 | 
			
		||||
      <div className="relative flex flex-1 flex-col">
 | 
			
		||||
        <AppHeader
 | 
			
		||||
          className="transition-opacity transition-duration-75"
 | 
			
		||||
          project={{ project, file }}
 | 
			
		||||
          enableMenu={true}
 | 
			
		||||
          nativeFileMenuCreated={nativeFileMenuCreated}
 | 
			
		||||
        >
 | 
			
		||||
          <CommandBarOpenButton />
 | 
			
		||||
          <ShareButton />
 | 
			
		||||
        </AppHeader>
 | 
			
		||||
        <div className="relative flex items-center flex-col">
 | 
			
		||||
          <AppHeader
 | 
			
		||||
            className="transition-opacity transition-duration-75"
 | 
			
		||||
            project={{ project, file }}
 | 
			
		||||
            enableMenu={true}
 | 
			
		||||
            nativeFileMenuCreated={nativeFileMenuCreated}
 | 
			
		||||
          >
 | 
			
		||||
            <CommandBarOpenButton />
 | 
			
		||||
            <ShareButton />
 | 
			
		||||
          </AppHeader>
 | 
			
		||||
          <Toolbar />
 | 
			
		||||
        </div>
 | 
			
		||||
        <ModalContainer />
 | 
			
		||||
        <ModelingSidebar />
 | 
			
		||||
        <EngineStream pool={pool} authToken={authToken} />
 | 
			
		||||
@ -273,18 +278,18 @@ export function App() {
 | 
			
		||||
        localItems={[
 | 
			
		||||
          ...(getSettings().app.showDebugPanel.current
 | 
			
		||||
            ? ([
 | 
			
		||||
                {
 | 
			
		||||
                  id: 'modeling-state',
 | 
			
		||||
                  element: 'text',
 | 
			
		||||
                  label:
 | 
			
		||||
                    modelingState.value instanceof Object
 | 
			
		||||
                      ? (xStateValueToString(modelingState.value) ?? '')
 | 
			
		||||
                      : modelingState.value,
 | 
			
		||||
                  toolTip: {
 | 
			
		||||
                    children: 'The current state of the modeler',
 | 
			
		||||
                  },
 | 
			
		||||
              {
 | 
			
		||||
                id: 'modeling-state',
 | 
			
		||||
                element: 'text',
 | 
			
		||||
                label:
 | 
			
		||||
                  modelingState.value instanceof Object
 | 
			
		||||
                    ? (xStateValueToString(modelingState.value) ?? '')
 | 
			
		||||
                    : modelingState.value,
 | 
			
		||||
                toolTip: {
 | 
			
		||||
                  children: 'The current state of the modeler',
 | 
			
		||||
                },
 | 
			
		||||
              ] satisfies StatusBarItemType[])
 | 
			
		||||
              },
 | 
			
		||||
            ] satisfies StatusBarItemType[])
 | 
			
		||||
            : []),
 | 
			
		||||
          {
 | 
			
		||||
            id: 'selection',
 | 
			
		||||
 | 
			
		||||
@ -203,7 +203,7 @@ export function Toolbar({
 | 
			
		||||
    <menu
 | 
			
		||||
      data-current-mode={currentMode}
 | 
			
		||||
      data-onboarding-id="toolbar"
 | 
			
		||||
      className="max-w-full whitespace-nowrap rounded-b px-2 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 relative border border-chalkboard-30 dark:border-chalkboard-80 border-t-0 shadow-sm"
 | 
			
		||||
      className="z-[19] max-w-full whitespace-nowrap rounded-b px-2 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 relative border border-chalkboard-30 dark:border-chalkboard-80 border-t-0 shadow-sm"
 | 
			
		||||
    >
 | 
			
		||||
      <ul
 | 
			
		||||
        {...props}
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@
 | 
			
		||||
  in Tailwind, such as complex grid layouts.
 | 
			
		||||
 */
 | 
			
		||||
.header {
 | 
			
		||||
  grid-template-columns: 1fr auto 1fr;
 | 
			
		||||
  user-select: none;
 | 
			
		||||
  -webkit-user-select: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,30 +1,30 @@
 | 
			
		||||
import { Toolbar } from '@src/Toolbar'
 | 
			
		||||
import { CommandBarOpenButton } from '@src/components/CommandBarOpenButton'
 | 
			
		||||
import ProjectSidebarMenu from '@src/components/ProjectSidebarMenu'
 | 
			
		||||
import UserSidebarMenu from '@src/components/UserSidebarMenu'
 | 
			
		||||
import { isDesktop } from '@src/lib/isDesktop'
 | 
			
		||||
import { type IndexLoaderData } from '@src/lib/types'
 | 
			
		||||
import type { IndexLoaderData } from '@src/lib/types'
 | 
			
		||||
import { useUser } from '@src/lib/singletons'
 | 
			
		||||
 | 
			
		||||
import styles from './AppHeader.module.css'
 | 
			
		||||
import type { ReactNode } from 'react'
 | 
			
		||||
 | 
			
		||||
interface AppHeaderProps extends React.PropsWithChildren {
 | 
			
		||||
  showToolbar?: boolean
 | 
			
		||||
  project?: Omit<IndexLoaderData, 'code'>
 | 
			
		||||
  className?: string
 | 
			
		||||
  enableMenu?: boolean
 | 
			
		||||
  style?: React.CSSProperties
 | 
			
		||||
  nativeFileMenuCreated: boolean
 | 
			
		||||
  projectMenuChildren?: ReactNode
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const AppHeader = ({
 | 
			
		||||
  showToolbar = true,
 | 
			
		||||
  project,
 | 
			
		||||
  children,
 | 
			
		||||
  className = '',
 | 
			
		||||
  style,
 | 
			
		||||
  enableMenu = false,
 | 
			
		||||
  nativeFileMenuCreated,
 | 
			
		||||
  projectMenuChildren,
 | 
			
		||||
}: AppHeaderProps) => {
 | 
			
		||||
  const user = useUser()
 | 
			
		||||
 | 
			
		||||
@ -32,14 +32,9 @@ export const AppHeader = ({
 | 
			
		||||
    <header
 | 
			
		||||
      id="app-header"
 | 
			
		||||
      data-testid="app-header"
 | 
			
		||||
      className={
 | 
			
		||||
        'w-full grid ' +
 | 
			
		||||
        styles.header +
 | 
			
		||||
        ` ${
 | 
			
		||||
          isDesktop() ? styles.desktopApp + ' ' : ''
 | 
			
		||||
        }overlaid-panes sticky top-0 z-20 px-2 items-start ` +
 | 
			
		||||
        className
 | 
			
		||||
      }
 | 
			
		||||
      className={`w-full flex ${styles.header || ''} ${
 | 
			
		||||
        isDesktop() ? styles.desktopApp : ''
 | 
			
		||||
      } overlaid-panes sticky top-0 z-20 px-2 justify-between ${className || ''} bg-chalkboard-10 dark:bg-chalkboard-90 border-b border-chalkboard-30 dark:border-chalkboard-70`}
 | 
			
		||||
      data-native-file-menu={nativeFileMenuCreated}
 | 
			
		||||
      style={style}
 | 
			
		||||
    >
 | 
			
		||||
@ -47,13 +42,9 @@ export const AppHeader = ({
 | 
			
		||||
        enableMenu={enableMenu}
 | 
			
		||||
        project={project?.project}
 | 
			
		||||
        file={project?.file}
 | 
			
		||||
      />
 | 
			
		||||
      {/* Toolbar if the context deems it */}
 | 
			
		||||
      <div className="flex flex-col items-center gap-2">
 | 
			
		||||
        <div className="flex-grow flex justify-center max-w-lg md:max-w-xl lg:max-w-2xl xl:max-w-4xl 2xl:max-w-5xl">
 | 
			
		||||
          {showToolbar && <Toolbar />}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      >
 | 
			
		||||
        {projectMenuChildren}
 | 
			
		||||
      </ProjectSidebarMenu>
 | 
			
		||||
      <div className="flex items-center gap-2 py-1 ml-auto">
 | 
			
		||||
        {/* If there are children, show them, otherwise show User menu */}
 | 
			
		||||
        {children || <CommandBarOpenButton />}
 | 
			
		||||
 | 
			
		||||
@ -10,13 +10,13 @@ export function CommandBarOpenButton() {
 | 
			
		||||
  return (
 | 
			
		||||
    <button
 | 
			
		||||
      type="button"
 | 
			
		||||
      className="flex gap-1 items-center py-0 px-0.5 m-0 text-primary dark:text-inherit bg-chalkboard-10/80 dark:bg-chalkboard-100/50 hover:bg-chalkboard-10 dark:hover:bg-chalkboard-100 border border-solid border-primary/50 hover:border-primary active:border-primary"
 | 
			
		||||
      className="flex gap-1 items-center py-0 pl-0.5 pr-1 sm:pr-0.5 m-0 text-primary dark:text-inherit bg-chalkboard-10/80 dark:bg-chalkboard-100/50 hover:bg-chalkboard-10 dark:hover:bg-chalkboard-100 border border-solid border-primary/50 hover:border-primary active:border-primary"
 | 
			
		||||
      onClick={() => commandBarActor.send({ type: 'Open' })}
 | 
			
		||||
      data-testid="command-bar-open-button"
 | 
			
		||||
    >
 | 
			
		||||
      <CustomIcon name="command" className="w-5 h-5" />
 | 
			
		||||
      <span>Commands</span>
 | 
			
		||||
      <kbd className="dark:bg-chalkboard-80 font-mono rounded-sm text-primary/70 dark:text-inherit inline-block px-1">
 | 
			
		||||
      <kbd className="hidden sm:block dark:bg-chalkboard-80 font-mono rounded-sm text-primary/70 dark:text-inherit inline-block px-1">
 | 
			
		||||
        {hotkeyDisplay(COMMAND_PALETTE_HOTKEY, platform)}
 | 
			
		||||
      </kbd>
 | 
			
		||||
    </button>
 | 
			
		||||
 | 
			
		||||
@ -17,29 +17,27 @@ import { APP_NAME } from '@src/lib/constants'
 | 
			
		||||
import { isDesktop } from '@src/lib/isDesktop'
 | 
			
		||||
import { PATHS } from '@src/lib/paths'
 | 
			
		||||
import { engineCommandManager, kclManager } from '@src/lib/singletons'
 | 
			
		||||
import { type IndexLoaderData } from '@src/lib/types'
 | 
			
		||||
import type { IndexLoaderData } from '@src/lib/types'
 | 
			
		||||
import { commandBarActor } from '@src/lib/singletons'
 | 
			
		||||
 | 
			
		||||
interface ProjectSidebarMenuProps extends React.PropsWithChildren {
 | 
			
		||||
  enableMenu?: boolean
 | 
			
		||||
  project?: IndexLoaderData['project']
 | 
			
		||||
  file?: IndexLoaderData['file']
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ProjectSidebarMenu = ({
 | 
			
		||||
  project,
 | 
			
		||||
  file,
 | 
			
		||||
  enableMenu = false,
 | 
			
		||||
}: {
 | 
			
		||||
  enableMenu?: boolean
 | 
			
		||||
  project?: IndexLoaderData['project']
 | 
			
		||||
  file?: IndexLoaderData['file']
 | 
			
		||||
}) => {
 | 
			
		||||
  children,
 | 
			
		||||
}: ProjectSidebarMenuProps) => {
 | 
			
		||||
  // Make room for traffic lights on desktop left side.
 | 
			
		||||
  // TODO: make sure this doesn't look like shit on Linux or Windows
 | 
			
		||||
  const trafficLightsOffset =
 | 
			
		||||
    isDesktop() && window.electron.os.isMac ? 'ml-20' : ''
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      className={
 | 
			
		||||
        '!no-underline h-full mr-auto max-h-min min-h-12 min-w-max flex items-center gap-2 ' +
 | 
			
		||||
        trafficLightsOffset
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
    <div className={'!no-underline flex gap-2 ' + trafficLightsOffset}>
 | 
			
		||||
      <AppLogoLink project={project} file={file} />
 | 
			
		||||
      {enableMenu ? (
 | 
			
		||||
        <ProjectMenuPopover project={project} file={file} />
 | 
			
		||||
@ -51,6 +49,7 @@ const ProjectSidebarMenu = ({
 | 
			
		||||
          {project?.name ? project.name : APP_NAME}
 | 
			
		||||
        </span>
 | 
			
		||||
      )}
 | 
			
		||||
      {children}
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
@ -64,7 +63,7 @@ function AppLogoLink({
 | 
			
		||||
}) {
 | 
			
		||||
  const { onProjectClose } = useLspContext()
 | 
			
		||||
  const wrapperClassName =
 | 
			
		||||
    "relative h-full grid place-content-center group p-1.5 before:block before:content-[''] before:absolute before:inset-0 before:bottom-2.5 before:z-[-1] before:bg-primary before:rounded-b-sm"
 | 
			
		||||
    "relative h-full grid flex-none place-content-center group p-1.5 before:block before:content-[''] before:absolute before:inset-0 before:bottom-1 before:z-[-1] before:bg-primary before:rounded-b-sm"
 | 
			
		||||
  const logoClassName = 'w-auto h-4 text-chalkboard-10'
 | 
			
		||||
 | 
			
		||||
  return isDesktop() ? (
 | 
			
		||||
@ -238,12 +237,23 @@ function ProjectMenuPopover({
 | 
			
		||||
  return (
 | 
			
		||||
    <Popover className="relative">
 | 
			
		||||
      <Popover.Button
 | 
			
		||||
        className="gap-1 rounded-sm h-9 mr-auto max-h-min min-w-max border-0 py-1 px-2 flex items-center  focus-visible:outline-appForeground dark:hover:bg-chalkboard-90"
 | 
			
		||||
        className="gap-1 rounded-sm mr-auto max-h-min min-w-max border-0 py-1 px-2 flex items-center  focus-visible:outline-appForeground dark:hover:bg-chalkboard-90"
 | 
			
		||||
        data-testid="project-sidebar-toggle"
 | 
			
		||||
      >
 | 
			
		||||
        <div className="flex flex-col items-start py-0.5">
 | 
			
		||||
        <div className="flex items-baseline py-0.5 text-sm gap-1">
 | 
			
		||||
          {isDesktop() && project?.name && (
 | 
			
		||||
            <>
 | 
			
		||||
              <span
 | 
			
		||||
                className="hidden whitespace-nowrap md:block"
 | 
			
		||||
                data-testid="app-header-project-name"
 | 
			
		||||
              >
 | 
			
		||||
                {project.name}
 | 
			
		||||
              </span>
 | 
			
		||||
              <span className="hidden md:block">/</span>
 | 
			
		||||
            </>
 | 
			
		||||
          )}
 | 
			
		||||
          <span
 | 
			
		||||
            className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block"
 | 
			
		||||
            className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap"
 | 
			
		||||
            data-testid="app-header-file-name"
 | 
			
		||||
          >
 | 
			
		||||
            {isDesktop() && file?.name
 | 
			
		||||
@ -252,14 +262,6 @@ function ProjectMenuPopover({
 | 
			
		||||
                )
 | 
			
		||||
              : APP_NAME}
 | 
			
		||||
          </span>
 | 
			
		||||
          {isDesktop() && project?.name && (
 | 
			
		||||
            <span
 | 
			
		||||
              className="hidden text-xs text-chalkboard-70 dark:text-chalkboard-40 whitespace-nowrap lg:block"
 | 
			
		||||
              data-testid="app-header-project-name"
 | 
			
		||||
            >
 | 
			
		||||
              {project.name}
 | 
			
		||||
            </span>
 | 
			
		||||
          )}
 | 
			
		||||
        </div>
 | 
			
		||||
        <CustomIcon
 | 
			
		||||
          name="caretDown"
 | 
			
		||||
 | 
			
		||||
@ -83,7 +83,7 @@ export const ShareButton = memo(function ShareButton() {
 | 
			
		||||
    billingContext.tier === undefined
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Popover className="relative flex">
 | 
			
		||||
    <Popover className="relative hidden sm:flex">
 | 
			
		||||
      <Popover.Button
 | 
			
		||||
        as="div"
 | 
			
		||||
        className="relative group border-0 w-fit min-w-max p-0 rounded-l-full focus-visible:outline-appForeground"
 | 
			
		||||
 | 
			
		||||
@ -178,9 +178,9 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Popover className="relative">
 | 
			
		||||
    <Popover className="relative grid">
 | 
			
		||||
      <Popover.Button
 | 
			
		||||
        className="relative group border-0 w-fit min-w-max p-0 rounded-l-full focus-visible:outline-appForeground"
 | 
			
		||||
        className="m-0 relative group border-0 w-fit min-w-max p-0 rounded-l-full rounded-r focus-visible:outline-appForeground"
 | 
			
		||||
        data-testid="user-sidebar-toggle"
 | 
			
		||||
      >
 | 
			
		||||
        <div className="flex items-center">
 | 
			
		||||
 | 
			
		||||
@ -221,10 +221,7 @@ const Home = () => {
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="relative flex flex-col items-stretch h-screen w-screen overflow-hidden">
 | 
			
		||||
      <AppHeader
 | 
			
		||||
        nativeFileMenuCreated={nativeFileMenuCreated}
 | 
			
		||||
        showToolbar={false}
 | 
			
		||||
      />
 | 
			
		||||
      <AppHeader nativeFileMenuCreated={nativeFileMenuCreated} />
 | 
			
		||||
      <div className="overflow-hidden self-stretch w-full flex-1 home-layout max-w-4xl lg:max-w-5xl xl:max-w-7xl px-4 mx-auto mt-8 lg:mt-24 lg:px-0">
 | 
			
		||||
        <HomeHeader
 | 
			
		||||
          setQuery={setQuery}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user