diff --git a/packages/playground/src/shared/dom/Scene.tsx b/packages/playground/src/shared/dom/Scene.tsx index 3b32743..65e6b54 100644 --- a/packages/playground/src/shared/dom/Scene.tsx +++ b/packages/playground/src/shared/dom/Scene.tsx @@ -1,7 +1,7 @@ import studio from '@theatre/studio' import type {UseDragOpts} from './useDrag' import useDrag from './useDrag' -import React, {useLayoutEffect, useMemo, useState} from 'react' +import React, {useLayoutEffect, useMemo, useRef, useState} from 'react' import type {IProject, ISheet} from '@theatre/core' import {onChange, types} from '@theatre/core' import type {IScrub, IStudio} from '@theatre/studio' @@ -19,6 +19,17 @@ const textInterpolate = (left: string, right: string, progression: number) => { return left } +const globalConfig = { + background: { + type: types.stringLiteral('black', { + black: 'black', + white: 'white', + dynamic: 'dynamic', + }), + dynamic: types.rgba(), + }, +} + const boxObjectConfig = { test: types.string('Hello?', {interpolate: textInterpolate}), testLiteral: types.stringLiteral('a', {a: 'Option A', b: 'Option B'}), @@ -162,8 +173,24 @@ export const Scene: React.FC<{project: IProject}> = ({project}) => { }) }) + const containerRef = useRef(null!) + + const globalObj = sheet.object('global', globalConfig) + + useLayoutEffect(() => { + const unsubscribeFromChanges = onChange(globalObj.props, (newValues) => { + console.log(newValues) + containerRef.current.style.background = + newValues.background.type !== 'dynamic' + ? newValues.background.type + : newValues.background.dynamic.toString() + }) + return unsubscribeFromChanges + }, [globalObj]) + return (
{ }) test('Undo/redo', async ({page}) => { - await page.locator('[data-testid="OutlinePanel-TriggerButton"]').click() - // https://github.com/microsoft/playwright/issues/12298 // The div does in fact intercept pointer events, but it is meant to 🤦‍ await page diff --git a/theatre/studio/src/panels/DetailPanel/DetailPanel.tsx b/theatre/studio/src/panels/DetailPanel/DetailPanel.tsx index a4749ae..4ce6a45 100644 --- a/theatre/studio/src/panels/DetailPanel/DetailPanel.tsx +++ b/theatre/studio/src/panels/DetailPanel/DetailPanel.tsx @@ -1,6 +1,11 @@ import {getOutlineSelection} from '@theatre/studio/selectors' import {usePrism, useVal} from '@theatre/react' -import React, {useEffect, useLayoutEffect} from 'react' +import React, { + createContext, + useContext, + useEffect, + useLayoutEffect, +} from 'react' import styled from 'styled-components' import {isProject, isSheetObject} from '@theatre/shared/instanceTypes' import { @@ -14,15 +19,19 @@ import ProjectDetails from './ProjectDetails' import getStudio from '@theatre/studio/getStudio' import useHotspot from '@theatre/studio/uiComponents/useHotspot' import {Box, prism, val} from '@theatre/dataverse' +import EmptyState from './EmptyState' +import useLockSet from '@theatre/studio/uiComponents/useLockSet' const headerHeight = `32px` const Container = styled.div<{pin: boolean}>` + ${pointerEventsAutoInNormalMode}; background-color: rgba(40, 43, 47, 0.8); position: fixed; right: 8px; top: 50px; - width: 236px; + // Temporary, see comment about CSS grid in SingleRowPropEditor. + width: 280px; height: fit-content; z-index: ${panelZIndexes.propsPanel}; @@ -68,14 +77,18 @@ const Body = styled.div` user-select: none; ` +export const contextMenuShownContext = createContext< + ReturnType +>([false, () => () => {}]) + const DetailPanel: React.FC<{}> = (props) => { const pin = useVal(getStudio().atomP.ahistoric.pinDetails) !== false - const hostspotActive = useHotspot('right') + const hotspotActive = useHotspot('right') useLayoutEffect(() => { - isDetailPanelHotspotActiveB.set(hostspotActive) - }, [hostspotActive]) + isDetailPanelHotspotActiveB.set(hotspotActive) + }, [hotspotActive]) // cleanup useEffect(() => { @@ -85,6 +98,10 @@ const DetailPanel: React.FC<{}> = (props) => { } }, []) + const [isContextMenuShown] = useContext(contextMenuShownContext) + + const showDetailsPanel = pin || hotspotActive || isContextMenuShown + return usePrism(() => { const selection = getOutlineSelection() @@ -93,7 +110,7 @@ const DetailPanel: React.FC<{}> = (props) => { return ( { isDetailPanelHoveredB.set(true) }} @@ -125,7 +142,7 @@ const DetailPanel: React.FC<{}> = (props) => { const project = selection.find(isProject) if (project) { return ( - +
<TitleBar_Piece>{project.address.projectId} </TitleBar_Piece> @@ -138,11 +155,31 @@ const DetailPanel: React.FC<{}> = (props) => { ) } - return <></> - }, [pin, hostspotActive]) + return ( + <Container + pin={showDetailsPanel} + onMouseEnter={() => { + isDetailPanelHoveredB.set(true) + }} + onMouseLeave={() => { + isDetailPanelHoveredB.set(false) + }} + > + <EmptyState /> + </Container> + ) + }, [showDetailsPanel]) } -export default DetailPanel +export default () => { + const lockSet = useLockSet() + + return ( + <contextMenuShownContext.Provider value={lockSet}> + <DetailPanel /> + </contextMenuShownContext.Provider> + ) +} const isDetailPanelHotspotActiveB = new Box<boolean>(false) const isDetailPanelHoveredB = new Box<boolean>(false) diff --git a/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/DetailCompoundPropEditor.tsx b/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/DetailCompoundPropEditor.tsx index da2986a..f8525d2 100644 --- a/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/DetailCompoundPropEditor.tsx +++ b/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/DetailCompoundPropEditor.tsx @@ -18,8 +18,8 @@ import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useCo import {useEditingToolsForCompoundProp} from '@theatre/studio/propEditors/useEditingToolsForCompoundProp' const Container = styled.div` - --step: 8px; - --left-pad: 0px; + --step: 15px; + --left-pad: 15px; ${pointerEventsAutoInNormalMode}; ` diff --git a/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor.tsx b/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor.tsx index c54c7c2..791981f 100644 --- a/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor.tsx +++ b/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor.tsx @@ -12,11 +12,19 @@ import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS export const indentationFormula = `calc(var(--left-pad) + var(--depth) * var(--step))` -const LeftRow = styled.div` +const Container = styled.div` display: flex; height: 30px; justify-content: flex-start; align-items: stretch; + // We cannot calculate both the container (details panel) width and the descendant + // (this) width dynamically. This leads to the container width being calculated + // without this percentage being taken into consideration leads to horizontal + // clipping/scrolling--the same way as if we explicitly fixed either the container + // width, or the descendant width. + // The correct solution for tabulated UIs with dynamic container widths is to use + // CSS grid. For now I fixed this issue by just giving a great enough width + // to the details panel so most things don't break. --right-width: 60%; position: relative; ${pointerEventsAutoInNormalMode}; @@ -97,7 +105,7 @@ export function SingleRowPropEditor<T>({ }) return ( - <LeftRow> + <Container> {contextMenu} <Left> <ControlsContainer>{editingTools.controlIndicators}</ControlsContainer> @@ -113,6 +121,6 @@ export function SingleRowPropEditor<T>({ </Left> <InputContainer>{children}</InputContainer> - </LeftRow> + </Container> ) } diff --git a/theatre/studio/src/panels/DetailPanel/EmptyState.tsx b/theatre/studio/src/panels/DetailPanel/EmptyState.tsx new file mode 100644 index 0000000..d2a219b --- /dev/null +++ b/theatre/studio/src/panels/DetailPanel/EmptyState.tsx @@ -0,0 +1,55 @@ +import type {FC} from 'react' +import React from 'react' +import styled from 'styled-components' +import {Outline} from '@theatre/studio/uiComponents/icons' + +const Container = styled.div` + padding: 16px; + display: flex; + flex-direction: column; + gap: 24px; +` + +const Message = styled.div` + display: flex; + flex-direction: column; + gap: 11px; + color: rgba(255, 255, 255, 0.9); +` + +const Icon = styled.div` + color: rgba(145, 145, 145, 0.8); +` + +const LinkToDoc = styled.a` + color: #919191; + font-size: 10px; + text-decoration-color: #40434a; + text-underline-offset: 3px; +` + +const EmptyState: FC = () => { + return ( + <Container> + <Message> + <Icon> + <Outline /> + </Icon> + <div> + Please select an object from the <u>Outline Menu</u> to see its + properties. + </div> + </Message> + {/* Links like this should probably be managed centrally so that we can + have a process for updating them when the docs change. */} + <LinkToDoc + href="https://docs.theatrejs.com/in-depth/#objects" + target="_blank" + > + Learn more about Objects + </LinkToDoc> + </Container> + ) +} + +export default EmptyState diff --git a/theatre/studio/src/panels/OutlinePanel/BaseItem.tsx b/theatre/studio/src/panels/OutlinePanel/BaseItem.tsx index aaa84fc..f6cf4f8 100644 --- a/theatre/studio/src/panels/OutlinePanel/BaseItem.tsx +++ b/theatre/studio/src/panels/OutlinePanel/BaseItem.tsx @@ -3,7 +3,7 @@ import React from 'react' import styled, {css} from 'styled-components' import noop from '@theatre/shared/utils/noop' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' -import {ChevronRight, Package} from '@theatre/studio/uiComponents/icons' +import {ChevronDown, Package} from '@theatre/studio/uiComponents/icons' export const Container = styled.li` margin: 0; @@ -22,10 +22,10 @@ const Header = styled(BaseHeader)` margin-top: 2px; margin-bottom: 2px; margin-left: calc(4px + var(--depth) * 16px); - padding-left: 8px; + padding-left: 4px; padding-right: 8px; - gap: 8px; - height: 23px; + gap: 4px; + height: 21px; line-height: 0; box-sizing: border-box; display: flex; @@ -86,7 +86,7 @@ const Head_Label = styled.span` ${pointerEventsAutoInNormalMode}; position: relative; // Compensate for border bottom - top: 1px; + top: 0.5px; display: flex; height: 20px; align-items: center; @@ -106,7 +106,6 @@ const Head_Icon_WithDescendants = styled.span<{isOpen: boolean}>` font-size: 9px; position: relative; display: block; - transform: rotateZ(${(props) => (props.isOpen ? 90 : 0)}deg); ` const ChildrenContainer = styled.ul` @@ -141,7 +140,7 @@ const BaseItem: React.FC<{ <Head_IconContainer> {canContainChildren ? ( <Head_Icon_WithDescendants isOpen={true}> - <ChevronRight /> + <ChevronDown /> </Head_Icon_WithDescendants> ) : ( <Package /> diff --git a/theatre/studio/src/panels/OutlinePanel/OutlinePanel.tsx b/theatre/studio/src/panels/OutlinePanel/OutlinePanel.tsx index 12730fc..913db4a 100644 --- a/theatre/studio/src/panels/OutlinePanel/OutlinePanel.tsx +++ b/theatre/studio/src/panels/OutlinePanel/OutlinePanel.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useState} from 'react' +import React, {useEffect, useLayoutEffect} from 'react' import styled from 'styled-components' import {panelZIndexes} from '@theatre/studio/panels/BasePanel/common' import ProjectsList from './ProjectsList/ProjectsList' @@ -6,6 +6,7 @@ import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import {useVal} from '@theatre/react' import getStudio from '@theatre/studio/getStudio' import useHotspot from '@theatre/studio/uiComponents/useHotspot' +import {Box, prism, val} from '@theatre/dataverse' const headerHeight = `44px` @@ -43,26 +44,31 @@ const Container = styled.div<{pin: boolean}>` } ` -const OutlinePanel: React.FC<{}> = (props) => { - const pin = useVal(getStudio().atomP.ahistoric.pinOutline) !== false - const show = useVal(getStudio().atomP.ephemeral.showOutline) +const OutlinePanel: React.FC<{}> = () => { + const pin = useVal(getStudio().atomP.ahistoric.pinOutline) ?? true + const show = useVal(shouldShowOutlineD) const active = useHotspot('left') - const [hovered, setHovered] = useState(false) + useLayoutEffect(() => { + isOutlinePanelHotspotActiveB.set(active) + }, [active]) + + // cleanup useEffect(() => { - getStudio().transaction(({stateEditors, drafts}) => { - stateEditors.studio.ephemeral.setShowOutline(active || hovered) - }) - }, [active, hovered]) + return () => { + isOutlinePanelHoveredB.set(false) + isOutlinePanelHotspotActiveB.set(false) + } + }, []) return ( <Container pin={pin || show} onMouseEnter={() => { - setHovered(true) + isOutlinePanelHoveredB.set(true) }} onMouseLeave={() => { - setHovered(false) + isOutlinePanelHoveredB.set(false) }} > <ProjectsList /> @@ -71,3 +77,13 @@ const OutlinePanel: React.FC<{}> = (props) => { } export default OutlinePanel + +const isOutlinePanelHotspotActiveB = new Box<boolean>(false) +const isOutlinePanelHoveredB = new Box<boolean>(false) + +export const shouldShowOutlineD = prism<boolean>(() => { + const isHovered = val(isOutlinePanelHoveredB.derivation) + const isHotspotActive = val(isOutlinePanelHotspotActiveB.derivation) + + return isHovered || isHotspotActive +}) diff --git a/theatre/studio/src/toolbars/GlobalToolbar.tsx b/theatre/studio/src/toolbars/GlobalToolbar.tsx index ead95f3..b7ad975 100644 --- a/theatre/studio/src/toolbars/GlobalToolbar.tsx +++ b/theatre/studio/src/toolbars/GlobalToolbar.tsx @@ -9,14 +9,9 @@ import BasicTooltip from '@theatre/studio/uiComponents/Popover/BasicTooltip' import {val} from '@theatre/dataverse' import ExtensionToolbar from './ExtensionToolbar/ExtensionToolbar' import PinButton from './PinButton' -import { - ChevronLeft, - ChevronRight, - Details, - Ellipsis, - Outline, -} from '@theatre/studio/uiComponents/icons' -import {shouldShowDetailD} from '@theatre/studio/panels/DetailPanel/DetailPanel' +import {Details, Ellipsis, Outline} from '@theatre/studio/uiComponents/icons' +import DoubleChevronLeft from '@theatre/studio/uiComponents/icons/DoubleChevronLeft' +import DoubleChevronRight from '@theatre/studio/uiComponents/icons/DoubleChevronRight' import ToolbarIconButton from '@theatre/studio/uiComponents/toolbar/ToolbarIconButton' import usePopover from '@theatre/studio/uiComponents/Popover/usePopover' import MoreMenu from './MoreMenu/MoreMenu' @@ -88,10 +83,8 @@ const GlobalToolbar: React.FC = () => { ), ) - const outlinePinned = useVal(getStudio().atomP.ahistoric.pinOutline) - const detailsPinned = useVal(getStudio().atomP.ahistoric.pinDetails) - const showOutline = useVal(getStudio().atomP.ephemeral.showOutline) - const showDetails = useVal(shouldShowDetailD) + const outlinePinned = useVal(getStudio().atomP.ahistoric.pinOutline) ?? true + const detailsPinned = useVal(getStudio().atomP.ahistoric.pinDetails) ?? true const hasUpdates = useVal(getStudio().atomP.ahistoric.updateChecker.result.hasUpdates) === true @@ -124,15 +117,14 @@ const GlobalToolbar: React.FC = () => { onClick={() => { getStudio().transaction(({stateEditors, drafts}) => { stateEditors.studio.ahistoric.setPinOutline( - !drafts.ahistoric.pinOutline, + !(drafts.ahistoric.pinOutline ?? true), ) }) }} icon={<Outline />} - pinHintIcon={<ChevronRight />} - unpinHintIcon={<ChevronLeft />} + pinHintIcon={<DoubleChevronRight />} + unpinHintIcon={<DoubleChevronLeft />} pinned={outlinePinned} - hint={showOutline} /> {conflicts.length > 0 ? ( <NumberOfConflictsIndicator> @@ -158,15 +150,14 @@ const GlobalToolbar: React.FC = () => { onClick={() => { getStudio().transaction(({stateEditors, drafts}) => { stateEditors.studio.ahistoric.setPinDetails( - !drafts.ahistoric.pinDetails, + !(drafts.ahistoric.pinDetails ?? true), ) }) }} icon={<Details />} - pinHintIcon={<ChevronLeft />} - unpinHintIcon={<ChevronRight />} + pinHintIcon={<DoubleChevronLeft />} + unpinHintIcon={<DoubleChevronRight />} pinned={detailsPinned} - hint={showDetails} /> </SubContainer> </Container> diff --git a/theatre/studio/src/toolbars/PinButton.tsx b/theatre/studio/src/toolbars/PinButton.tsx index 373cb21..c8a8272 100644 --- a/theatre/studio/src/toolbars/PinButton.tsx +++ b/theatre/studio/src/toolbars/PinButton.tsx @@ -1,7 +1,7 @@ import styled from 'styled-components' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import type {ComponentPropsWithRef, ReactNode} from 'react' -import React, {forwardRef} from 'react' +import React, {forwardRef, useState} from 'react' const Container = styled.button<{pinned?: boolean}>` ${pointerEventsAutoInNormalMode}; @@ -15,13 +15,15 @@ const Container = styled.button<{pinned?: boolean}>` height: 32px; outline: none; - color: #a8a8a9; + color: ${({pinned}) => (pinned ? 'rgba(255, 255, 255, 0.8)' : '#A8A8A9')}; - background: ${({pinned}) => - pinned ? 'rgba(40, 43, 47, 0.9)' : 'rgba(40, 43, 47, 0.45)'}; + background: rgba(40, 43, 47, 0.8); backdrop-filter: blur(14px); border: none; - border-bottom: 1px solid rgba(255, 255, 255, 0.08); + border-bottom: 1px solid + ${({pinned}) => + pinned ? 'rgba(255, 255, 255, 0.7)' : 'rgba(255, 255, 255, 0.08)'}; + border-radius: 2px; &:hover { background: rgba(59, 63, 69, 0.8); @@ -31,20 +33,8 @@ const Container = styled.button<{pinned?: boolean}>` background: rgba(82, 88, 96, 0.8); } - @supports not (backdrop-filter: blur()) { - background: rgba(40, 43, 47, 0.8); - - &:hover { - background: rgba(59, 63, 69, 0.8); - } - - &:active { - background: rgba(82, 88, 96, 0.7); - } - - &.selected { - background: rgb(27, 32, 35); - } + svg { + display: block; } ` @@ -58,9 +48,33 @@ interface PinButtonProps extends ComponentPropsWithRef<'button'> { const PinButton = forwardRef<HTMLButtonElement, PinButtonProps>( ({hint, pinned, icon, pinHintIcon, unpinHintIcon, ...props}, ref) => { + const [hovered, setHovered] = useState(false) + + const showHint = hovered || hint + return ( - <Container {...props} pinned={pinned} ref={ref}> - {hint && !pinned ? pinHintIcon : hint && pinned ? unpinHintIcon : icon} + <Container + {...props} + pinned={pinned} + ref={ref} + onMouseOver={() => setHovered(true)} + onMouseOut={() => setHovered(false)} + > + {/* Necessary for hover to work properly. */} + <div + style={{ + pointerEvents: 'none', + width: 'fit-content', + height: 'fit-content', + inset: 0, + }} + > + {showHint && !pinned + ? pinHintIcon + : showHint && pinned + ? unpinHintIcon + : icon} + </div> </Container> ) }, diff --git a/theatre/studio/src/uiComponents/Popover/usePopover.tsx b/theatre/studio/src/uiComponents/Popover/usePopover.tsx index d400a0d..f284069 100644 --- a/theatre/studio/src/uiComponents/Popover/usePopover.tsx +++ b/theatre/studio/src/uiComponents/Popover/usePopover.tsx @@ -5,6 +5,7 @@ import {createPortal} from 'react-dom' import {PortalContext} from 'reakit' import type {AbsolutePlacementBoxConstraints} from './TooltipWrapper' import TooltipWrapper from './TooltipWrapper' +import {contextMenuShownContext} from '@theatre/studio/panels/DetailPanel/DetailPanel' export type OpenFn = ( e: React.MouseEvent | MouseEvent | {clientX: number; clientY: number}, @@ -113,6 +114,18 @@ export default function usePopover( state, }) + // TODO: this lock is now exported from the detail panel, do refactor it when you get the chance + const [, addContextMenu] = useContext(contextMenuShownContext) + + useEffect(() => { + let removeContextMenu: () => void | undefined + if (state.isOpen) { + removeContextMenu = addContextMenu() + } + + return () => removeContextMenu?.() + }, [state.isOpen]) + const portalLayer = useContext(PortalContext) const node = state.isOpen ? ( diff --git a/theatre/studio/src/uiComponents/icons/ArrowClockwise.tsx b/theatre/studio/src/uiComponents/icons/ArrowClockwise.tsx index bc196c4..8b22cf9 100644 --- a/theatre/studio/src/uiComponents/icons/ArrowClockwise.tsx +++ b/theatre/studio/src/uiComponents/icons/ArrowClockwise.tsx @@ -12,8 +12,7 @@ function ArrowClockwise(props: React.SVGProps<SVGSVGElement>) { > <path d="M5.586 3.38a5 5 0 015.45 1.087L12.574 6h-1.572a.5.5 0 000 1h3a.5.5 0 00.5-.5v-3a.5.5 0 10-1 0v2.013l-1.76-1.754a6 6 0 100 8.482.5.5 0 10-.707-.707A5 5 0 115.587 3.38z" - fill="#fff" - fillOpacity={0.6} + fill="currentColor" /> </svg> ) diff --git a/theatre/studio/src/uiComponents/icons/ArrowsOutCardinal.tsx b/theatre/studio/src/uiComponents/icons/ArrowsOutCardinal.tsx index 0cb0ee9..f76d13f 100644 --- a/theatre/studio/src/uiComponents/icons/ArrowsOutCardinal.tsx +++ b/theatre/studio/src/uiComponents/icons/ArrowsOutCardinal.tsx @@ -12,8 +12,7 @@ function ArrowsOutCardinal(props: React.SVGProps<SVGSVGElement>) { > <path d="M8 1a.498.498 0 01.358.15l1.764 1.765a.5.5 0 01-.707.707L8.5 2.707V6a.5.5 0 01-1 0V2.707l-.915.915a.5.5 0 11-.707-.707l1.768-1.769A.498.498 0 018 1zM8 9.5a.5.5 0 01.5.5v3.292l.915-.915a.5.5 0 01.707.707L8.37 14.836a.499.499 0 01-.74.001l-1.752-1.753a.5.5 0 01.707-.707l.915.915V10a.5.5 0 01.5-.5zM3.622 6.584a.5.5 0 10-.707-.707L1.146 7.646a.498.498 0 00.018.724l1.751 1.752a.5.5 0 10.707-.708L2.708 8.5H6a.5.5 0 000-1H2.706l.916-.916zM12.378 5.877a.5.5 0 01.707 0l1.768 1.769a.498.498 0 01-.017.724l-1.751 1.752a.5.5 0 01-.707-.708l.914-.914H10a.5.5 0 010-1h3.294l-.916-.916a.5.5 0 010-.707z" - fill="#fff" - fillOpacity={0.6} + fill="currentColor" /> </svg> ) diff --git a/theatre/studio/src/uiComponents/icons/Camera.tsx b/theatre/studio/src/uiComponents/icons/Camera.tsx index db6f244..26dc8a4 100644 --- a/theatre/studio/src/uiComponents/icons/Camera.tsx +++ b/theatre/studio/src/uiComponents/icons/Camera.tsx @@ -14,15 +14,13 @@ function Camera(props: React.SVGProps<SVGSVGElement>) { fillRule="evenodd" clipRule="evenodd" d="M7.767 5.75a2.75 2.75 0 100 5.5 2.75 2.75 0 000-5.5zM6.017 8.5a1.75 1.75 0 113.5 0 1.75 1.75 0 01-3.5 0z" - fill="#fff" - fillOpacity={0.6} + fill="currentColor" /> <path fillRule="evenodd" clipRule="evenodd" d="M5.773 2.25a.5.5 0 00-.416.223l-.85 1.277H2.782a1.496 1.496 0 00-1.497 1.5v7a1.501 1.501 0 001.497 1.5h9.972a1.496 1.496 0 001.498-1.5v-7a1.501 1.501 0 00-1.498-1.5h-1.726l-.849-1.277a.5.5 0 00-.416-.223H5.773zm-.58 2.277L6.04 3.25h3.453l.849 1.277a.5.5 0 00.416.223h1.994a.496.496 0 01.498.5v7a.501.501 0 01-.498.5H2.781a.495.495 0 01-.497-.5v-7a.501.501 0 01.497-.5h1.995a.5.5 0 00.416-.223z" - fill="#fff" - fillOpacity={0.6} + fill="currentColor" /> </svg> ) diff --git a/theatre/studio/src/uiComponents/icons/ChevronDown.tsx b/theatre/studio/src/uiComponents/icons/ChevronDown.tsx index b2843cc..dde68b6 100644 --- a/theatre/studio/src/uiComponents/icons/ChevronDown.tsx +++ b/theatre/studio/src/uiComponents/icons/ChevronDown.tsx @@ -14,8 +14,7 @@ function ChevronDown(props: React.SVGProps<SVGSVGElement>) { fillRule="evenodd" clipRule="evenodd" d="M8 10.5L4 6.654 5.2 5.5 8 8.385 10.8 5.5 12 6.654 8 10.5z" - fill="#fff" - fillOpacity={0.6} + fill="currentColor" /> </svg> ) diff --git a/theatre/studio/src/uiComponents/icons/ChevronLeft.tsx b/theatre/studio/src/uiComponents/icons/ChevronLeft.tsx index 6472fb9..60043a3 100644 --- a/theatre/studio/src/uiComponents/icons/ChevronLeft.tsx +++ b/theatre/studio/src/uiComponents/icons/ChevronLeft.tsx @@ -12,8 +12,7 @@ function ChevronLeft(props: React.SVGProps<SVGSVGElement>) { > <path d="M10.45 2.266l.956.954-4.763 4.763 4.763 4.762-.955.954-5.712-5.716 5.712-5.717z" - fill="#fff" - fillOpacity={0.6} + fill="currentColor" /> </svg> ) diff --git a/theatre/studio/src/uiComponents/icons/ChevronRight.tsx b/theatre/studio/src/uiComponents/icons/ChevronRight.tsx index ec5b38f..2865834 100644 --- a/theatre/studio/src/uiComponents/icons/ChevronRight.tsx +++ b/theatre/studio/src/uiComponents/icons/ChevronRight.tsx @@ -12,8 +12,7 @@ function ChevronRight(props: React.SVGProps<SVGSVGElement>) { > <path d="M5.694 2.266l-.955.954 4.763 4.763-4.763 4.762.955.954 5.712-5.716-5.712-5.717z" - fill="#fff" - fillOpacity={0.6} + fill="currentColor" /> </svg> ) diff --git a/theatre/studio/src/uiComponents/icons/Cube.tsx b/theatre/studio/src/uiComponents/icons/Cube.tsx index 610e43d..1207d97 100644 --- a/theatre/studio/src/uiComponents/icons/Cube.tsx +++ b/theatre/studio/src/uiComponents/icons/Cube.tsx @@ -14,8 +14,7 @@ function Cube(props: React.SVGProps<SVGSVGElement>) { fillRule="evenodd" clipRule="evenodd" d="M7.51.953L2.01 4.046c-.314.178-.51.51-.511.87v6.168c.002.354.202.695.51.87l5.5 3.093c.298.171.682.171.98 0l5.5-3.093c.308-.175.508-.516.51-.87V4.919a1.008 1.008 0 00-.51-.872L8.49.953a1.003 1.003 0 00-.98 0zm5.474 3.674L8 1.824l-4.977 2.8 5.03 2.804 4.93-2.8zM2.5 5.477v5.605l5.007 2.816.047-5.604L2.5 5.477zm6.007 8.414l4.99-2.807.003-5.6-4.946 2.81-.047 5.597z" - fill="#fff" - fillOpacity={0.6} + fill="currentColor" /> </svg> ) diff --git a/theatre/studio/src/uiComponents/icons/CubeFull.tsx b/theatre/studio/src/uiComponents/icons/CubeFull.tsx index 28d738d..d11183f 100644 --- a/theatre/studio/src/uiComponents/icons/CubeFull.tsx +++ b/theatre/studio/src/uiComponents/icons/CubeFull.tsx @@ -12,8 +12,7 @@ function CubeFull(props: React.SVGProps<SVGSVGElement>) { > <path d="M2.135 4.253l5.968 3.241 5.746-3.241-5.252-3.064a1 1 0 00-.993-.008L2.135 4.253zM7.586 14.947V8.338l-5.922-3.25v5.918a1 1 0 00.507.87l5.415 3.071zM8.414 14.947V8.338l5.922-3.25v5.918a1 1 0 01-.507.87l-5.415 3.071z" - fill="#fff" - fillOpacity={0.6} + fill="currentColor" /> </svg> ) diff --git a/theatre/studio/src/uiComponents/icons/CubeHalf.tsx b/theatre/studio/src/uiComponents/icons/CubeHalf.tsx index e030654..0466542 100644 --- a/theatre/studio/src/uiComponents/icons/CubeHalf.tsx +++ b/theatre/studio/src/uiComponents/icons/CubeHalf.tsx @@ -12,8 +12,7 @@ function CubeHalf(props: React.SVGProps<SVGSVGElement>) { > <path d="M13.988 4.05L8.488.958a1.015 1.015 0 00-.975 0l-5.5 3.094c-.286.161-.514.533-.513.868v6.163c0 .356.202.7.513.875l5.5 3.094c.3.164.671.168.975 0l5.5-3.094c.31-.175.511-.519.512-.875V4.919c.002-.327-.223-.705-.512-.868zM8.056 7.427l-5.031-2.8L8 1.826l4.981 2.8-4.925 2.8zm5.444 3.656l-4.994 2.813.05-5.6L13.5 5.481v5.6z" - fill="#fff" - fillOpacity={0.6} + fill="currentColor" /> </svg> ) diff --git a/theatre/studio/src/uiComponents/icons/CubeRendered.tsx b/theatre/studio/src/uiComponents/icons/CubeRendered.tsx index c6577a8..2395f93 100644 --- a/theatre/studio/src/uiComponents/icons/CubeRendered.tsx +++ b/theatre/studio/src/uiComponents/icons/CubeRendered.tsx @@ -13,14 +13,12 @@ function CubeRendered(props: React.SVGProps<SVGSVGElement>) { <path d="M8.202 2.973l-2.92 1.58 2.714 1.58 2.817-1.58-2.61-1.58zM3.532 10.555v-3.22l3.031 1.72v3l-3.03-1.5zM12.532 10.555v-3.22l-3.031 1.72v3l3.031-1.5z" fill="#fff" - fillOpacity={0.6} /> <path fillRule="evenodd" clipRule="evenodd" d="M7.51.955c.298-.171.682-.171.98 0l5.5 3.093c.315.179.508.51.51.87v6.165a1.024 1.024 0 01-.51.873l-5.5 3.093a1.003 1.003 0 01-.98 0L2.01 11.957a1.025 1.025 0 01-.511-.874V4.918c.002-.355.203-.696.51-.87L7.51.955zm.49.871l4.982 2.802-4.928 2.8-5.03-2.803L8 1.826zm-5.5 9.255V5.477l5.054 2.817-.047 5.606L2.5 11.08zm6.007 2.812l4.99-2.807.003-5.602-4.946 2.81-.047 5.599z" - fill="#fff" - fillOpacity={0.6} + fill="currentColor" /> </svg> ) diff --git a/theatre/studio/src/uiComponents/icons/Details.tsx b/theatre/studio/src/uiComponents/icons/Details.tsx index eccce15..3f57dc4 100644 --- a/theatre/studio/src/uiComponents/icons/Details.tsx +++ b/theatre/studio/src/uiComponents/icons/Details.tsx @@ -14,7 +14,7 @@ function Details(props: React.SVGProps<SVGSVGElement>) { fillRule="evenodd" clipRule="evenodd" d="M3.5 3c-1.072 0-1.969.904-1.969 1.969 0 1 .929 1.968 1.969 1.968h9A1.969 1.969 0 1012.5 3h-9zm9 1H5.531v1.938H12.5A.969.969 0 0012.5 4zM3.5 9.14a1.969 1.969 0 000 3.938h9a1.969 1.969 0 100-3.937h-9zm9 1H8.406v1.938H12.5a.969.969 0 100-1.937z" - fill="#A7A8A9" + fill="currentColor" /> </svg> ) diff --git a/theatre/studio/src/uiComponents/icons/DoubleChevronLeft.tsx b/theatre/studio/src/uiComponents/icons/DoubleChevronLeft.tsx new file mode 100644 index 0000000..f468b12 --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/DoubleChevronLeft.tsx @@ -0,0 +1,23 @@ +import * as React from 'react' + +function DoubleChevronLeft(props: React.SVGProps<SVGSVGElement>) { + return ( + <svg + width={16} + height={16} + viewBox="0 0 16 16" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path + fillRule="evenodd" + clipRule="evenodd" + d="M12.732 4.048l-.792-.792L7.2 8l4.74 4.744.792-.792L8.781 8l3.951-3.952zm-3.932 0l-.792-.792L3.268 8l4.74 4.744.792-.792L4.848 8 8.8 4.048z" + fill="currentColor" + /> + </svg> + ) +} + +export default DoubleChevronLeft diff --git a/theatre/studio/src/uiComponents/icons/DoubleChevronRight.tsx b/theatre/studio/src/uiComponents/icons/DoubleChevronRight.tsx new file mode 100644 index 0000000..aeae6a6 --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/DoubleChevronRight.tsx @@ -0,0 +1,23 @@ +import * as React from 'react' + +function DoubleChevronRight(props: React.SVGProps<SVGSVGElement>) { + return ( + <svg + width={16} + height={16} + viewBox="0 0 16 16" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path + fillRule="evenodd" + clipRule="evenodd" + d="M3.694 3.765l.792-.792 4.74 4.744-4.74 4.744-.792-.793 3.951-3.951-3.951-3.952zm3.932 0l.792-.792 4.74 4.744-4.74 4.744-.792-.793 3.952-3.951-3.952-3.952z" + fill="currentColor" + /> + </svg> + ) +} + +export default DoubleChevronRight diff --git a/theatre/studio/src/uiComponents/icons/Ellipsis.tsx b/theatre/studio/src/uiComponents/icons/Ellipsis.tsx index 1530873..7670a07 100644 --- a/theatre/studio/src/uiComponents/icons/Ellipsis.tsx +++ b/theatre/studio/src/uiComponents/icons/Ellipsis.tsx @@ -14,8 +14,7 @@ function Ellipsis(props: React.SVGProps<SVGSVGElement>) { fillRule="evenodd" clipRule="evenodd" d="M.166 7.994a2.26 2.26 0 114.518 0 2.26 2.26 0 01-4.518 0zM2.425 6.91a1.085 1.085 0 100 2.17 1.085 1.085 0 000-2.17zM5.74 7.994a2.26 2.26 0 114.519 0 2.26 2.26 0 01-4.519 0zM8 6.91a1.085 1.085 0 100 2.17 1.085 1.085 0 000-2.17zM13.575 5.735a2.26 2.26 0 100 4.519 2.26 2.26 0 000-4.52zm-1.086 2.26a1.085 1.085 0 112.171 0 1.085 1.085 0 01-2.17 0z" - fill="#fff" - fillOpacity={0.6} + fill="currentColor" /> </svg> ) diff --git a/theatre/studio/src/uiComponents/icons/GlobeSimple.tsx b/theatre/studio/src/uiComponents/icons/GlobeSimple.tsx index cb29324..94b2799 100644 --- a/theatre/studio/src/uiComponents/icons/GlobeSimple.tsx +++ b/theatre/studio/src/uiComponents/icons/GlobeSimple.tsx @@ -14,8 +14,7 @@ function GlobeSimple(props: React.SVGProps<SVGSVGElement>) { fillRule="evenodd" clipRule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zm4.75-5.216A5.505 5.505 0 002.522 7.5H5.01c.051-1.468.328-2.807.764-3.825.14-.326.298-.626.477-.89zM13.478 7.5A5.505 5.505 0 009.75 2.784c.179.265.338.565.477.891.436 1.018.713 2.357.764 3.825h2.487zm0 1a5.505 5.505 0 01-3.729 4.716c.18-.264.339-.566.478-.892.436-1.018.713-2.356.764-3.824h2.487zM6.25 13.216A5.505 5.505 0 012.522 8.5H5.01c.051 1.468.328 2.806.764 3.824.14.326.299.627.478.892zm.44-9.147c-.374.874-.63 2.074-.682 3.431h3.982c-.051-1.357-.308-2.557-.683-3.431-.21-.491-.448-.857-.686-1.092-.236-.234-.446-.315-.622-.315s-.386.081-.622.315c-.238.235-.476.6-.686 1.092zm2.617 7.862c.375-.875.631-2.075.683-3.431H6.009c.052 1.356.308 2.556.683 3.43.21.492.448.857.686 1.093.236.233.446.314.622.314s.386-.081.622-.314c.238-.236.476-.601.686-1.092z" - fill="#fff" - fillOpacity={0.6} + fill="currentColor" /> </svg> ) diff --git a/theatre/studio/src/uiComponents/icons/Outline.tsx b/theatre/studio/src/uiComponents/icons/Outline.tsx index f7344b5..fa148d2 100644 --- a/theatre/studio/src/uiComponents/icons/Outline.tsx +++ b/theatre/studio/src/uiComponents/icons/Outline.tsx @@ -14,7 +14,7 @@ function Outline(props: React.SVGProps<SVGSVGElement>) { fillRule="evenodd" clipRule="evenodd" d="M1.775 2.781a.5.5 0 01.5.5v1.7H4.67c.108-.957.92-1.7 1.905-1.7h6.608a1.917 1.917 0 110 3.834H6.574c-.78 0-1.452-.466-1.751-1.135H2.275v5.03h2.39a2.032 2.032 0 012.023-1.854h6.38a2.031 2.031 0 110 4.063h-6.38c-.83 0-1.543-.497-1.858-1.21H1.775a.5.5 0 01-.5-.5V3.281a.5.5 0 01.5-.5zm4.799 1.5h6.608a.917.917 0 110 1.834H6.574a.917.917 0 110-1.834zm.114 5.875h6.38a1.031 1.031 0 110 2.063h-6.38a1.032 1.032 0 110-2.063z" - fill="#A8A8A9" + fill="currentColor" /> </svg> ) diff --git a/theatre/studio/src/uiComponents/icons/Package.tsx b/theatre/studio/src/uiComponents/icons/Package.tsx index 4f38323..0f6952f 100644 --- a/theatre/studio/src/uiComponents/icons/Package.tsx +++ b/theatre/studio/src/uiComponents/icons/Package.tsx @@ -12,8 +12,7 @@ function Package(props: React.SVGProps<SVGSVGElement>) { > <path d="M8.339 4.5l-2.055.644 4.451 1.393v2.748l-2.966.928-2.504-.783V6.738l2.42.758 2.055-.644-4.458-1.395L4 5.858v4.463L7.768 11.5 12 10.175V5.646L8.339 4.5z" - fill="#fff" - fillOpacity={0.6} + fill="currentColor" /> </svg> ) diff --git a/theatre/studio/src/uiComponents/icons/Resize.tsx b/theatre/studio/src/uiComponents/icons/Resize.tsx index 4f045d2..e357da5 100644 --- a/theatre/studio/src/uiComponents/icons/Resize.tsx +++ b/theatre/studio/src/uiComponents/icons/Resize.tsx @@ -14,13 +14,11 @@ function Resize(props: React.SVGProps<SVGSVGElement>) { fillRule="evenodd" clipRule="evenodd" d="M1.452 3.452a2 2 0 012-2h9.096a2 2 0 012 2v9.096a2 2 0 01-2 2H3.452a2 2 0 01-2-2V3.452zm2-1h9.096a1 1 0 011 1v9.096a1 1 0 01-1 1h-5.06V8.511H2.451V3.452a1 1 0 011-1z" - fill="#fff" - fillOpacity={0.6} + fill="currentColor" /> <path d="M12.501 4.09a.5.5 0 00-.5-.5H8.95a.5.5 0 100 1h1.98l-2.45 2.449a.5.5 0 10.707.707l2.315-2.315v1.627a.5.5 0 001 0V4.09z" - fill="#fff" - fillOpacity={0.6} + fill="currentColor" /> </svg> ) diff --git a/theatre/studio/src/uiComponents/simpleContextMenu/useContextMenu.tsx b/theatre/studio/src/uiComponents/simpleContextMenu/useContextMenu.tsx index e343370..8f8db24 100644 --- a/theatre/studio/src/uiComponents/simpleContextMenu/useContextMenu.tsx +++ b/theatre/studio/src/uiComponents/simpleContextMenu/useContextMenu.tsx @@ -1,5 +1,5 @@ import type {VoidFn} from '@theatre/shared/utils/types' -import React, {useEffect} from 'react' +import React, {useContext, useEffect} from 'react' import ContextMenu from './ContextMenu/ContextMenu' import type { IContextMenuItemsValue, @@ -7,6 +7,7 @@ import type { } from './ContextMenu/ContextMenu' import useRequestContextMenu from './useRequestContextMenu' import type {IRequestContextMenuOptions} from './useRequestContextMenu' +import {contextMenuShownContext} from '@theatre/studio/panels/DetailPanel/DetailPanel' // re-exports export type { @@ -27,10 +28,17 @@ export default function useContextMenu( ): [node: React.ReactNode, close: VoidFn, isOpen: boolean] { const [status, close] = useRequestContextMenu(target, opts) + // TODO: this lock is now exported from the detail panel, do refactor it when you get the chance + const [, addContextMenu] = useContext(contextMenuShownContext) + useEffect(() => { + let removeContextMenu: () => void | undefined if (status.isOpen) { opts.onOpen?.() + removeContextMenu = addContextMenu() } + + return () => removeContextMenu?.() }, [status.isOpen, opts.onOpen]) const node = !status.isOpen ? ( diff --git a/theatre/studio/src/uiComponents/toolbar/ToolbarIconButton.tsx b/theatre/studio/src/uiComponents/toolbar/ToolbarIconButton.tsx index ee6d747..c50f847 100644 --- a/theatre/studio/src/uiComponents/toolbar/ToolbarIconButton.tsx +++ b/theatre/studio/src/uiComponents/toolbar/ToolbarIconButton.tsx @@ -21,10 +21,11 @@ export const Container = styled.button` color: #a8a8a9; - background: rgba(40, 43, 47, 0.45); + background: rgba(40, 43, 47, 0.8); backdrop-filter: blur(14px); border: none; border-bottom: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 2px; filter: drop-shadow(0px 1px 1px rgba(0, 0, 0, 0.25)) drop-shadow(0px 2px 6px rgba(0, 0, 0, 0.15)); @@ -38,18 +39,8 @@ export const Container = styled.button` } &.selected { - background: rgba(40, 43, 47, 0.9); - color: white; - } - - &:first-child { - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; - } - - &:last-child { - border-bottom-right-radius: 2px; - border-top-right-radius: 2px; + color: rgba(255, 255, 255, 0.8); + border-bottom: 1px solid rgba(255, 255, 255, 0.7); } // Don't blur if in a button group, because it already blurs. We need to blur @@ -57,21 +48,16 @@ export const Container = styled.button` ${ToolbarSwitchSelectContainer} > & { backdrop-filter: none; filter: none; - } + border-radius: 0; - @supports not (backdrop-filter: blur()) { - background: rgba(40, 43, 47, 0.8); - - &:hover { - background: rgba(59, 63, 69, 0.8); + &:first-child { + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; } - &:active { - background: rgba(82, 88, 96, 0.7); - } - - &.selected { - background: rgb(27, 32, 35); + &:last-child { + border-bottom-right-radius: 2px; + border-top-right-radius: 2px; } } ` diff --git a/theatre/studio/src/uiComponents/useHotspot.ts b/theatre/studio/src/uiComponents/useHotspot.ts index 83919f7..cf043e4 100644 --- a/theatre/studio/src/uiComponents/useHotspot.ts +++ b/theatre/studio/src/uiComponents/useHotspot.ts @@ -7,9 +7,15 @@ export default function useHotspot(spot: 'left' | 'right') { const hoverListener = (e: MouseEvent) => { const threshold = active ? 200 : 50 - const mouseInside = + // This is a super specific solution just for now so that the hotspot region + // excludes the pin button. + const topBuffer = 56 + + let mouseInside = spot === 'left' ? e.x < threshold : e.x > window.innerWidth - threshold + mouseInside &&= e.y > topBuffer + if (mouseInside) { setActive(true) } else {