From 0690a85ae2d828a180f73ee5016c5570e5ff5c88 Mon Sep 17 00:00:00 2001 From: Andrew Prifer <2991360+AndrewPrifer@users.noreply.github.com> Date: Wed, 25 May 2022 20:42:01 +0200 Subject: [PATCH] UI improvements/sidebar pinning (#175) Co-authored-by: Aria Minaei --- .../tests/setting-static-props/test.e2e.ts | 7 +- .../ReferenceWindow/ReferenceWindow.tsx | 2 - .../r3f/src/components/SnapshotEditor.tsx | 63 +++--- .../src/components/TooltipPortalProvider.tsx | 27 --- packages/r3f/src/extension.ts | 42 ++-- theatre/studio/src/UI.ts | 8 +- theatre/studio/src/UIRoot/UIRoot.tsx | 2 +- .../src/panels/DetailPanel/DetailPanel.tsx | 170 ++++++++------- .../DetailCompoundPropEditor.tsx | 7 +- .../SingleRowPropEditor.tsx | 31 +-- .../src/panels/DetailPanel/ProjectDetails.tsx | 5 +- .../src/panels/OutlinePanel/BaseItem.tsx | 113 ++++------ .../src/panels/OutlinePanel/OutlinePanel.tsx | 201 ++++-------------- .../simpleEditors/RgbaPropEditor.tsx | 5 +- .../src/propEditors/utils/propNameTextCSS.tsx | 2 +- theatre/studio/src/store/index.ts | 1 + theatre/studio/src/store/stateEditors.ts | 16 ++ theatre/studio/src/store/types/ahistoric.ts | 8 + theatre/studio/src/store/types/ephemeral.ts | 21 ++ .../ExtensionToolbar.tsx} | 29 +-- .../Toolset.tsx | 0 .../tools/IconButton.tsx | 0 .../tools/Switch.tsx | 0 theatre/studio/src/toolbars/GlobalToolbar.tsx | 130 +++++++++++ theatre/studio/src/toolbars/PinButton.tsx | 69 ++++++ .../src/uiComponents/icons/ArrowClockwise.tsx | 22 ++ .../uiComponents/icons/ArrowsOutCardinal.tsx | 22 ++ .../studio/src/uiComponents/icons/Camera.tsx | 31 +++ .../src/uiComponents/icons/ChevronDown.tsx | 24 +++ .../src/uiComponents/icons/ChevronLeft.tsx | 22 ++ .../src/uiComponents/icons/ChevronRight.tsx | 22 ++ .../studio/src/uiComponents/icons/Cube.tsx | 24 +++ .../src/uiComponents/icons/CubeFull.tsx | 22 ++ .../src/uiComponents/icons/CubeHalf.tsx | 22 ++ .../src/uiComponents/icons/CubeRendered.tsx | 29 +++ .../studio/src/uiComponents/icons/Details.tsx | 23 ++ .../src/uiComponents/icons/Ellipsis.tsx | 24 +++ .../src/uiComponents/icons/GlobeSimple.tsx | 24 +++ .../studio/src/uiComponents/icons/Outline.tsx | 23 ++ .../studio/src/uiComponents/icons/Package.tsx | 22 ++ .../studio/src/uiComponents/icons/Resize.tsx | 29 +++ .../studio/src/uiComponents/icons/index.ts | 16 ++ .../toolbar/ToolbarIconButton.tsx | 77 ++++--- .../toolbar/ToolbarSwitchSelect.tsx | 15 +- .../toolbar/ToolbarSwitchSelectContainer.ts | 13 ++ theatre/studio/src/uiComponents/useHotspot.ts | 34 +++ 46 files changed, 998 insertions(+), 501 deletions(-) delete mode 100644 packages/r3f/src/components/TooltipPortalProvider.tsx rename theatre/studio/src/toolbars/{GlobalToolbar/GlobalToolbar.tsx => ExtensionToolbar/ExtensionToolbar.tsx} (79%) rename theatre/studio/src/toolbars/{GlobalToolbar => ExtensionToolbar}/Toolset.tsx (100%) rename theatre/studio/src/toolbars/{GlobalToolbar => ExtensionToolbar}/tools/IconButton.tsx (100%) rename theatre/studio/src/toolbars/{GlobalToolbar => ExtensionToolbar}/tools/Switch.tsx (100%) create mode 100644 theatre/studio/src/toolbars/GlobalToolbar.tsx create mode 100644 theatre/studio/src/toolbars/PinButton.tsx create mode 100644 theatre/studio/src/uiComponents/icons/ArrowClockwise.tsx create mode 100644 theatre/studio/src/uiComponents/icons/ArrowsOutCardinal.tsx create mode 100644 theatre/studio/src/uiComponents/icons/Camera.tsx create mode 100644 theatre/studio/src/uiComponents/icons/ChevronDown.tsx create mode 100644 theatre/studio/src/uiComponents/icons/ChevronLeft.tsx create mode 100644 theatre/studio/src/uiComponents/icons/ChevronRight.tsx create mode 100644 theatre/studio/src/uiComponents/icons/Cube.tsx create mode 100644 theatre/studio/src/uiComponents/icons/CubeFull.tsx create mode 100644 theatre/studio/src/uiComponents/icons/CubeHalf.tsx create mode 100644 theatre/studio/src/uiComponents/icons/CubeRendered.tsx create mode 100644 theatre/studio/src/uiComponents/icons/Details.tsx create mode 100644 theatre/studio/src/uiComponents/icons/Ellipsis.tsx create mode 100644 theatre/studio/src/uiComponents/icons/GlobeSimple.tsx create mode 100644 theatre/studio/src/uiComponents/icons/Outline.tsx create mode 100644 theatre/studio/src/uiComponents/icons/Package.tsx create mode 100644 theatre/studio/src/uiComponents/icons/Resize.tsx create mode 100644 theatre/studio/src/uiComponents/icons/index.ts create mode 100644 theatre/studio/src/uiComponents/toolbar/ToolbarSwitchSelectContainer.ts create mode 100644 theatre/studio/src/uiComponents/useHotspot.ts diff --git a/packages/playground/src/tests/setting-static-props/test.e2e.ts b/packages/playground/src/tests/setting-static-props/test.e2e.ts index 475fee3..31d17d3 100644 --- a/packages/playground/src/tests/setting-static-props/test.e2e.ts +++ b/packages/playground/src/tests/setting-static-props/test.e2e.ts @@ -12,7 +12,12 @@ test.describe('setting-static-props', () => { test('Undo/redo', async ({page}) => { await page.locator('[data-testid="OutlinePanel-TriggerButton"]').click() - await page.locator('span:has-text("sample object")').first().click() + // https://github.com/microsoft/playwright/issues/12298 + // The div does in fact intercept pointer events, but it is meant to 🤦‍ + await page + .locator('span:has-text("sample object")') + .first() + .click({force: true}) const detailPanel = page.locator('[data-testid="DetailPanel-Object"]') diff --git a/packages/r3f/src/components/ReferenceWindow/ReferenceWindow.tsx b/packages/r3f/src/components/ReferenceWindow/ReferenceWindow.tsx index 06c4fa9..3c9a9e0 100644 --- a/packages/r3f/src/components/ReferenceWindow/ReferenceWindow.tsx +++ b/packages/r3f/src/components/ReferenceWindow/ReferenceWindow.tsx @@ -99,8 +99,6 @@ const ReferenceWindow: VFC = ({height}) => { const ctx = canvasRef.current!.getContext('2d')! - // console.log(gl.domElement.getContext('webgl2')!.getContextAttributes()) - // https://stackoverflow.com/questions/17861447/html5-canvas-drawimage-how-to-apply-antialiasing ctx.imageSmoothingQuality = 'high' diff --git a/packages/r3f/src/components/SnapshotEditor.tsx b/packages/r3f/src/components/SnapshotEditor.tsx index 364b6f4..368d6a6 100644 --- a/packages/r3f/src/components/SnapshotEditor.tsx +++ b/packages/r3f/src/components/SnapshotEditor.tsx @@ -15,7 +15,6 @@ import {getEditorSheet, getEditorSheetObject} from './editorStuff' import type {$IntentionalAny} from '@theatre/shared/utils/types' import {InfiniteGridHelper} from '../InfiniteGridHelper' import {DragDetectorProvider} from './DragDetector' -import TooltipPortalProvider from './TooltipPortalProvider' import ReferenceWindow from './ReferenceWindow/ReferenceWindow' const GlobalStyle = createGlobalStyle` @@ -182,39 +181,37 @@ const SnapshotEditor: React.FC<{paneId: string}> = (props) => { <> - - - - - {showReferenceWindow && ( - - - - )} - + + + + {showReferenceWindow && ( + + + + )} + - {sceneSnapshot ? ( - <> - - { - gl.setClearColor('white') - }} - shadows - dpr={[1, 2]} - frameloop="demand" - onPointerMissed={onPointerMissed} - > - - - - - ) : null} - - + {sceneSnapshot ? ( + <> + + { + gl.setClearColor('white') + }} + shadows + dpr={[1, 2]} + frameloop="demand" + onPointerMissed={onPointerMissed} + > + + + + + ) : null} + diff --git a/packages/r3f/src/components/TooltipPortalProvider.tsx b/packages/r3f/src/components/TooltipPortalProvider.tsx deleted file mode 100644 index c7edd62..0000000 --- a/packages/r3f/src/components/TooltipPortalProvider.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import type {ReactNode} from 'react' -import React, {useState} from 'react' -import {PortalContext} from 'reakit' -import styled from 'styled-components' - -const PortalHost = styled.div` - position: fixed; - inset: 0; - pointer-events: none; -` - -export interface PortalManagerProps { - children: ReactNode -} - -const TooltipPortalProvider: React.VFC = ({children}) => { - const [wrapper, setWrapper] = useState(null) - - return ( - - {children} - - - ) -} - -export default TooltipPortalProvider diff --git a/packages/r3f/src/extension.ts b/packages/r3f/src/extension.ts index e3ce143..733475a 100644 --- a/packages/r3f/src/extension.ts +++ b/packages/r3f/src/extension.ts @@ -21,6 +21,27 @@ const r3fExtension: IExtension = { id: '@theatre/r3f', toolbars: { global(set, studio) { + const calc = prism(() => { + const editorObject = getEditorSheetObject() + + return [ + { + type: 'Icon', + title: 'Create Snapshot', + svgSource: io5CameraOutline, + onClick: () => { + studio.createPane('snapshot') + }, + }, + ] + }) + return calc.tapImmediate(Ticker.raf, () => { + set(calc.getValue()) + }) + }, + 'snapshot-editor': (set, studio) => { + const {createSnapshot} = useEditorStore.getState() + const calc = prism(() => { const editorObject = getEditorSheetObject() @@ -34,11 +55,9 @@ const r3fExtension: IExtension = { return [ { type: 'Icon', - title: 'Create Snapshot', - svgSource: io5CameraOutline, - onClick: () => { - studio.createPane('snapshot') - }, + onClick: createSnapshot, + title: 'Refresh Snapshot', + svgSource: ``, }, { type: 'Switch', @@ -121,19 +140,6 @@ const r3fExtension: IExtension = { set(calc.getValue()) }) }, - 'snapshot-editor': (set) => { - const {createSnapshot} = useEditorStore.getState() - const onClick = createSnapshot - set([ - { - type: 'Icon', - onClick, - title: 'Refresh Snapshot', - svgSource: ``, - }, - ]) - return () => {} - }, }, panes: [ { diff --git a/theatre/studio/src/UI.ts b/theatre/studio/src/UI.ts index 86cd396..c39518e 100644 --- a/theatre/studio/src/UI.ts +++ b/theatre/studio/src/UI.ts @@ -5,8 +5,8 @@ import ReactDOM from 'react-dom' import type {Studio} from './Studio' import {val} from '@theatre/dataverse' import {getMounter} from './utils/renderInPortalInContext' -import {Toolbar} from './toolbars/GlobalToolbar/GlobalToolbar' import {withStyledShadow} from './css' +import ExtensionToolbar from './toolbars/ExtensionToolbar/ExtensionToolbar' export default class UI { readonly containerEl = document.createElement('div') @@ -93,7 +93,11 @@ export default class UI { renderToolset(toolsetId: string, htmlNode: HTMLElement) { const s = getMounter() - s.mountOrRender(withStyledShadow(Toolbar), {toolbarId: toolsetId}, htmlNode) + s.mountOrRender( + withStyledShadow(ExtensionToolbar), + {toolbarId: toolsetId}, + htmlNode, + ) return s.unmount } diff --git a/theatre/studio/src/UIRoot/UIRoot.tsx b/theatre/studio/src/UIRoot/UIRoot.tsx index 190a152..758825f 100644 --- a/theatre/studio/src/UIRoot/UIRoot.tsx +++ b/theatre/studio/src/UIRoot/UIRoot.tsx @@ -4,7 +4,7 @@ import {val} from '@theatre/dataverse' import React, {useEffect} from 'react' import styled, {createGlobalStyle} from 'styled-components' import PanelsRoot from './PanelsRoot' -import GlobalToolbar from '@theatre/studio/toolbars/GlobalToolbar/GlobalToolbar' +import GlobalToolbar from '@theatre/studio/toolbars/GlobalToolbar' import useRefAndState from '@theatre/studio/utils/useRefAndState' import {PortalContext} from 'reakit' import type {$IntentionalAny} from '@theatre/shared/utils/types' diff --git a/theatre/studio/src/panels/DetailPanel/DetailPanel.tsx b/theatre/studio/src/panels/DetailPanel/DetailPanel.tsx index 07dc03b..a4749ae 100644 --- a/theatre/studio/src/panels/DetailPanel/DetailPanel.tsx +++ b/theatre/studio/src/panels/DetailPanel/DetailPanel.tsx @@ -1,6 +1,6 @@ import {getOutlineSelection} from '@theatre/studio/selectors' -import {usePrism} from '@theatre/react' -import React from 'react' +import {usePrism, useVal} from '@theatre/react' +import React, {useEffect, useLayoutEffect} from 'react' import styled from 'styled-components' import {isProject, isSheetObject} from '@theatre/shared/instanceTypes' import { @@ -11,46 +11,35 @@ import { import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import ObjectDetails from './ObjectDetails' import ProjectDetails from './ProjectDetails' +import getStudio from '@theatre/studio/getStudio' +import useHotspot from '@theatre/studio/uiComponents/useHotspot' +import {Box, prism, val} from '@theatre/dataverse' -const Container = styled.div` - background-color: transparent; - pointer-events: none; +const headerHeight = `32px` + +const Container = styled.div<{pin: boolean}>` + background-color: rgba(40, 43, 47, 0.8); position: fixed; - left: 0; - right: 0; - top: 12px; - bottom: 0px; + right: 8px; + top: 50px; + width: 236px; + height: fit-content; z-index: ${panelZIndexes.propsPanel}; - &:before { + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.25), 0 2px 6px rgba(0, 0, 0, 0.15); + backdrop-filter: blur(14px); + border-radius: 2px; + + display: ${({pin}) => (pin ? 'block' : 'none')}; + + &:hover { display: block; - content: ' '; - position: absolute; - top: 0; - bottom: 0; - right: 0; - width: 20px; - ${pointerEventsAutoInNormalMode}; - } -` - -const Content = styled.div` - position: absolute; - top: 0; - right: 0; - width: 260px; - bottom: 0; - /* transform: translateX(100%); */ - /* pointer-events: none; */ - - ${Container}:hover & { - transform: translateX(0); } ` const Title = styled.div` margin: 0 10px; - color: #ffffffc2; + color: #919191; font-weight: 500; font-size: 10px; user-select: none; @@ -60,37 +49,15 @@ const Title = styled.div` text-overflow: ellipsis; ` -const headerHeight = `32px` - const Header = styled.div` height: ${headerHeight}; display: flex; align-items: center; - position: absolute; - top: 0; - left: 0; - right: 0; - - &:after { - position: absolute; - inset: 1px 0px; - display: block; - content: ' '; - pointer-events: none; - z-index: -1; - background-color: #262c2dd1; - /* border-radius: 2px 0 0 2px; */ - } ` const Body = styled.div` ${pointerEventsAutoInNormalMode}; - position: absolute; - top: ${headerHeight}; - left: 0; - right: 0; - height: auto; - max-height: calc(100% - ${headerHeight}); + max-height: calc(100vh - 100px); overflow-y: scroll; &::-webkit-scrollbar { display: none; @@ -102,56 +69,87 @@ const Body = styled.div` ` const DetailPanel: React.FC<{}> = (props) => { + const pin = useVal(getStudio().atomP.ahistoric.pinDetails) !== false + + const hostspotActive = useHotspot('right') + + useLayoutEffect(() => { + isDetailPanelHotspotActiveB.set(hostspotActive) + }, [hostspotActive]) + + // cleanup + useEffect(() => { + return () => { + isDetailPanelHoveredB.set(false) + isDetailPanelHotspotActiveB.set(false) + } + }, []) + return usePrism(() => { const selection = getOutlineSelection() const obj = selection.find(isSheetObject) if (obj) { return ( - - -
- ${obj.address.objectKey}`} - > - <TitleBar_Piece>{obj.sheet.address.sheetId} </TitleBar_Piece> + <Container + data-testid="DetailPanel-Object" + pin={pin || hostspotActive} + onMouseEnter={() => { + isDetailPanelHoveredB.set(true) + }} + onMouseLeave={() => { + isDetailPanelHoveredB.set(false) + }} + > + <Header> + <Title + title={`${obj.sheet.address.sheetId}: ${obj.sheet.address.sheetInstanceId} > ${obj.address.objectKey}`} + > + <TitleBar_Piece>{obj.sheet.address.sheetId} </TitleBar_Piece> - <TitleBar_Punctuation>{':'} </TitleBar_Punctuation> - <TitleBar_Piece> - {obj.sheet.address.sheetInstanceId}{' '} - </TitleBar_Piece> + <TitleBar_Punctuation>{':'} </TitleBar_Punctuation> + <TitleBar_Piece> + {obj.sheet.address.sheetInstanceId}{' '} + </TitleBar_Piece> - <TitleBar_Punctuation> {'>'} </TitleBar_Punctuation> - <TitleBar_Piece>{obj.address.objectKey}</TitleBar_Piece> - -
- - - -
+  →  + {obj.address.objectKey} + + + + +
) } const project = selection.find(isProject) if (project) { return ( - - -
- - <TitleBar_Piece>{project.address.projectId} </TitleBar_Piece> - -
- - - -
+ +
+ + <TitleBar_Piece>{project.address.projectId} </TitleBar_Piece> + +
+ + +
) } return <> - }, []) + }, [pin, hostspotActive]) } export default DetailPanel + +const isDetailPanelHotspotActiveB = new Box(false) +const isDetailPanelHoveredB = new Box(false) + +export const shouldShowDetailD = prism(() => { + const isHovered = val(isDetailPanelHoveredB.derivation) + const isHotspotActive = val(isDetailPanelHotspotActiveB.derivation) + + return isHovered || isHotspotActive +}) diff --git a/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/DetailCompoundPropEditor.tsx b/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/DetailCompoundPropEditor.tsx index f02c8e1..b42e7f6 100644 --- a/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/DetailCompoundPropEditor.tsx +++ b/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/DetailCompoundPropEditor.tsx @@ -6,10 +6,7 @@ import last from 'lodash-es/last' import {darken, transparentize} from 'polished' import React from 'react' import styled from 'styled-components' -import { - indentationFormula, - rowBg, -} from '@theatre/studio/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor' +import {indentationFormula} from '@theatre/studio/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor' import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS' import DefaultOrStaticValueIndicator from '@theatre/studio/propEditors/DefaultValueIndicator' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' @@ -29,8 +26,6 @@ const Header = styled.div` display: flex; align-items: stretch; position: relative; - - ${rowBg}; ` const Padding = styled.div` diff --git a/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor.tsx b/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor.tsx index 2ecbd0e..c54c7c2 100644 --- a/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor.tsx +++ b/theatre/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor.tsx @@ -6,39 +6,12 @@ import useRefAndState from '@theatre/studio/utils/useRefAndState' import {last} from 'lodash-es' import React from 'react' import type {useEditingToolsForSimplePropInDetailsPanel} from '@theatre/studio/propEditors/useEditingToolsForSimpleProp' -import styled, {css} from 'styled-components' -import {transparentize} from 'polished' +import styled from 'styled-components' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS' export const indentationFormula = `calc(var(--left-pad) + var(--depth) * var(--step))` -export const rowBgColor = transparentize(0.05, '#282b2f') - -export const rowBg = css` - &:after, - &:before { - position: absolute; - display: block; - content: ' '; - z-index: -1; - box-sizing: content-box; - } - - &:after { - inset: 0px 0 1px calc(-2px + var(--left-pad) + var(--depth) * var(--step)); - background-color: ${rowBgColor}; - } - - &:before { - height: 2px; - right: 0; - bottom: 0px; - left: calc(-2px + var(--left-pad) + var(--depth) * var(--step)); - background-color: ${transparentize(0.2, rowBgColor)}; - } -` - const LeftRow = styled.div` display: flex; height: 30px; @@ -47,8 +20,6 @@ const LeftRow = styled.div` --right-width: 60%; position: relative; ${pointerEventsAutoInNormalMode}; - - ${rowBg}; ` const Left = styled.div` diff --git a/theatre/studio/src/panels/DetailPanel/ProjectDetails.tsx b/theatre/studio/src/panels/DetailPanel/ProjectDetails.tsx index ee86871..aeb347c 100644 --- a/theatre/studio/src/panels/DetailPanel/ProjectDetails.tsx +++ b/theatre/studio/src/panels/DetailPanel/ProjectDetails.tsx @@ -5,12 +5,9 @@ import usePopover from '@theatre/studio/uiComponents/Popover/usePopover' import React, {useCallback, useState} from 'react' import styled from 'styled-components' import DetailPanelButton from '@theatre/studio/uiComponents/DetailPanelButton' -import {rowBgColor} from './DeterminePropEditorForDetail/SingleRowPropEditor' import StateConflictRow from './ProjectDetails/StateConflictRow' -const Container = styled.div` - background-color: ${rowBgColor}; -` +const Container = styled.div`` const TheExportRow = styled.div` padding: 8px 10px; diff --git a/theatre/studio/src/panels/OutlinePanel/BaseItem.tsx b/theatre/studio/src/panels/OutlinePanel/BaseItem.tsx index 60dba9a..aaa84fc 100644 --- a/theatre/studio/src/panels/OutlinePanel/BaseItem.tsx +++ b/theatre/studio/src/panels/OutlinePanel/BaseItem.tsx @@ -1,9 +1,9 @@ import type {VoidFn} from '@theatre/shared/utils/types' import React from 'react' -import {GoChevronRight, DiHtml53DEffects} from 'react-icons/all' 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' export const Container = styled.li` margin: 0; @@ -17,14 +17,16 @@ export const Container = styled.li` export const BaseHeader = styled.div`` -const baseBg = `#3e4447` - -const baseBorderColor = `#34343e` - const Header = styled(BaseHeader)` - padding-left: calc(4px + var(--depth) * 16px); + position: relative; + margin-top: 2px; + margin-bottom: 2px; + margin-left: calc(4px + var(--depth) * 16px); + padding-left: 8px; padding-right: 8px; - height: 28px; + gap: 8px; + height: 23px; + line-height: 0; box-sizing: border-box; display: flex; flex-wrap: nowrap; @@ -32,29 +34,42 @@ const Header = styled(BaseHeader)` pointer-events: none; white-space: nowrap; - color: rgba(255, 255, 255, 0.75); - --item-bg: ${baseBg}; - --item-border-color: ${baseBorderColor}; + border-radius: 2px; + box-shadow: 0 3px 4px -1px rgba(0, 0, 0, 0.48); + + color: rgba(255, 255, 255, 0.9); + background: rgba(40, 43, 47, 0.65); + backdrop-filter: blur(14px); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); &.descendant-is-selected { - color: rgba(255, 255, 255, 0.9); - - --item-bg: #2e4244ed; - --item-border-color: #254355; + background: rgba(29, 53, 59, 0.7); } - &:not(.not-selectable):hover { - color: #fff; + &:not(.not-selectable):not(.selected):hover { + background: rgba(59, 63, 69, 0.9); - --item-bg: #1e5866; - --item-border-color: #152f42; + border-bottom: 1px solid rgba(255, 255, 255, 0.24); + } + + &:not(.not-selectable):not(.selected):active { + background: rgba(82, 88, 96, 0.9); + border-bottom: 1px solid rgba(255, 255, 255, 0.24); } &.selected { - color: rgba(255, 255, 255, 0.9); + background: rgba(30, 88, 102, 0.7); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + } - --item-bg: #1e5866; - --item-border-color: #152f42; + // Hit zone + &:before { + position: absolute; + inset: -1px -20px; + display: block; + content: ' '; + z-index: 5; + ${pointerEventsAutoInNormalMode}; } ` @@ -68,61 +83,23 @@ export const outlineItemFont = css` const Head_Label = styled.span` ${outlineItemFont}; - padding: 2px 8px; ${pointerEventsAutoInNormalMode}; position: relative; + // Compensate for border bottom + top: 1px; display: flex; - height: 17px; + height: 20px; align-items: center; - - background-color: var(--item-bg); - - &:after { - border: 1px solid var(--item-border-color); - position: absolute; - inset: 0px; - display: block; - content: ' '; - z-index: -1; - pointer-events: none; - border-radius: 2px; - box-sizing: border-box; - box-shadow: 0px 3px 4px -1px rgba(0, 0, 0, 0.48); - } - - // hit-zone - &:before { - position: absolute; - inset: -1px -20px; - display: block; - content: ' '; - z-index: 0; - ${pointerEventsAutoInNormalMode}; - } + box-sizing: border-box; ` -const Head_IconContainer = styled.span` - width: 18px; - box-sizing: border-box; - height: 18px; - margin-right: 4px; +const Head_IconContainer = styled.div` font-weight: 500; display: flex; - align-items: center; justify-content: center; + align-items: center; position: relative; opacity: 0.99; - - &:after { - display: block; - content: ' '; - position: absolute; - inset: 0px; - z-index: -1; - background-color: var(--item-bg); - opacity: 0.6; - border-radius: 2px; - } ` const Head_Icon_WithDescendants = styled.span<{isOpen: boolean}>` @@ -160,14 +137,14 @@ const BaseItem: React.FC<{ {'--depth': depth} } > -
+
{canContainChildren ? ( - + ) : ( - + )} diff --git a/theatre/studio/src/panels/OutlinePanel/OutlinePanel.tsx b/theatre/studio/src/panels/OutlinePanel/OutlinePanel.tsx index b6c456d..12730fc 100644 --- a/theatre/studio/src/panels/OutlinePanel/OutlinePanel.tsx +++ b/theatre/studio/src/panels/OutlinePanel/OutlinePanel.tsx @@ -1,188 +1,71 @@ -import React from 'react' +import React, {useEffect, useState} from 'react' import styled from 'styled-components' import {panelZIndexes} from '@theatre/studio/panels/BasePanel/common' import ProjectsList from './ProjectsList/ProjectsList' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' -import ToolbarIconButton from '@theatre/studio/uiComponents/toolbar/ToolbarIconButton' -import {VscListTree} from 'react-icons/all' -import {usePrism} from '@theatre/react' +import {useVal} from '@theatre/react' import getStudio from '@theatre/studio/getStudio' -import {val} from '@theatre/dataverse' -import useTooltip from '@theatre/studio/uiComponents/Popover/useTooltip' -import BasicTooltip from '@theatre/studio/uiComponents/Popover/BasicTooltip' -import ErrorTooltip from '@theatre/studio/uiComponents/Popover/ErrorTooltip' +import useHotspot from '@theatre/studio/uiComponents/useHotspot' -const Container = styled.div` +const headerHeight = `44px` + +const Container = styled.div<{pin: boolean}>` background-color: transparent; - pointer-events: none; position: absolute; - left: 0; - top: 12px; - bottom: 0px; - right: 0; - z-index: ${panelZIndexes.outlinePanel}; - - &:before { - display: block; - content: ' '; - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 40px; - ${pointerEventsAutoInNormalMode}; - } - &:hover:before { - top: -12px; - width: 300px; - } -` - -const TriggerContainer = styled.div` - margin-left: 12px; - position: relative; - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; -` - -const Content = styled.div` - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - transform: translateX(-100%); - pointer-events: none; - - ${Container}:hover & { - transform: translateX(0); - } -` - -const headerHeight = `32px` - -const TriggerButton = styled(ToolbarIconButton)` - ${Container}:hover & { - background-color: rgba(36, 38, 42, 0.95); - &:after { - border-color: rgba(255, 255, 255, 0.22); - } - color: white; - } -` - -const Title = styled.div` - margin: 0 12px; - color: #ffffffc2; - font-weight: 500; - font-size: 10px; - user-select: none; - position: relative; - display: none; - background-color: rgba(60, 60, 60, 0.2); - height: 24px; - ${Container}:hover & { - display: block; - } - - &:after { - position: absolute; - inset: 4px 0px; - display: block; - content: ' '; - pointer-events: none; - z-index: -1; - background-color: #69777947; - border-radius: 0 2px 2px 0; - } -` - -const Body = styled.div` - ${pointerEventsAutoInNormalMode}; - position: absolute; - top: ${headerHeight}; left: 8px; - height: auto; + z-index: ${panelZIndexes.outlinePanel}; + ${pointerEventsAutoInNormalMode}; + top: calc(${headerHeight} + 8px); + height: fit-content; max-height: calc(100% - ${headerHeight}); overflow-y: scroll; overflow-x: hidden; padding: 0; user-select: none; + &::-webkit-scrollbar { display: none; } scrollbar-width: none; -` -const NumberOfConflictsIndicator = styled.div` - color: white; - width: 14px; - height: 14px; - background: #d00; - border-radius: 4px; - text-align: center; - line-height: 14px; - font-weight: 600; - font-size: 8px; - position: relative; - left: -6px; - top: -11px; - margin-right: -14px; - box-shadow: 0 4px 6px -4px #00000059; + display: ${({pin}) => (pin ? 'block' : 'none')}; + + &:hover { + display: block; + } + + // Create a small buffer on the bottom to aid selecting the bottom item in a long, scrolling list + &::after { + content: ''; + display: block; + height: 20px; + } ` const OutlinePanel: React.FC<{}> = (props) => { - const conflicts = usePrism(() => { - const ephemeralStateOfAllProjects = val( - getStudio().atomP.ephemeral.coreByProject, - ) - return Object.entries(ephemeralStateOfAllProjects) - .map(([projectId, state]) => ({projectId, state})) - .filter( - ({state}) => - state.loadingState.type === 'browserStateIsNotBasedOnDiskState', - ) - }, []) + const pin = useVal(getStudio().atomP.ahistoric.pinOutline) !== false + const show = useVal(getStudio().atomP.ephemeral.showOutline) + const active = useHotspot('left') + const [hovered, setHovered] = useState(false) - const [triggerTooltip, triggerButtonRef] = useTooltip( - {enabled: conflicts.length > 0, enterDelay: conflicts.length > 0 ? 0 : 200}, - () => - conflicts.length > 0 ? ( - - {conflicts.length === 1 - ? `There is a state conflict in project "${conflicts[0].projectId}". Select the project in the outline below in order to fix it.` - : `There are ${conflicts.length} projects that have state conflicts. They are highlighted in the outline below. `} - - ) : ( - Outline - ), - ) + useEffect(() => { + getStudio().transaction(({stateEditors, drafts}) => { + stateEditors.studio.ephemeral.setShowOutline(active || hovered) + }) + }, [active, hovered]) return ( - - - {triggerTooltip} - - - - {conflicts.length > 0 ? ( - - {conflicts.length} - - ) : null} - {/* Outline */} - - - - - - + { + setHovered(true) + }} + onMouseLeave={() => { + setHovered(false) + }} + > + ) } diff --git a/theatre/studio/src/propEditors/simpleEditors/RgbaPropEditor.tsx b/theatre/studio/src/propEditors/simpleEditors/RgbaPropEditor.tsx index e9e43e5..67b879e 100644 --- a/theatre/studio/src/propEditors/simpleEditors/RgbaPropEditor.tsx +++ b/theatre/studio/src/propEditors/simpleEditors/RgbaPropEditor.tsx @@ -33,7 +33,7 @@ const ColorPreviewPuck = styled.div.attrs((props) => ({ }))` height: 18px; aspect-ratio: 1; - border-radius: 2px; + border-radius: 99999px; ` const HexInput = styled(BasicStringInput)` @@ -46,14 +46,13 @@ const RgbaPopover = styled.div` position: absolute; background-color: ${popoverBackgroundColor}; color: white; - padding: 0; margin: 0; cursor: default; border-radius: 3px; z-index: 10000; backdrop-filter: blur(8px); - padding: 4; + padding: 4px; pointer-events: all; border: none; diff --git a/theatre/studio/src/propEditors/utils/propNameTextCSS.tsx b/theatre/studio/src/propEditors/utils/propNameTextCSS.tsx index 6ce482a..5ca35a0 100644 --- a/theatre/studio/src/propEditors/utils/propNameTextCSS.tsx +++ b/theatre/studio/src/propEditors/utils/propNameTextCSS.tsx @@ -3,6 +3,6 @@ import {css} from 'styled-components' export const propNameTextCSS = css` font-weight: 300; font-size: 11px; - color: #9a9a9a; + color: #919191; text-shadow: 0.5px 0.5px 2px rgba(0, 0, 0, 0.3); ` diff --git a/theatre/studio/src/store/index.ts b/theatre/studio/src/store/index.ts index d35ba8c..10c06ed 100644 --- a/theatre/studio/src/store/index.ts +++ b/theatre/studio/src/store/index.ts @@ -41,6 +41,7 @@ const initialState: StudioState = { byId: {}, paneClasses: {}, }, + showOutline: false, }, } diff --git a/theatre/studio/src/store/stateEditors.ts b/theatre/studio/src/store/stateEditors.ts index 76e5452..132638a 100644 --- a/theatre/studio/src/store/stateEditors.ts +++ b/theatre/studio/src/store/stateEditors.ts @@ -42,6 +42,7 @@ import type { OutlineSelectionState, PanelPosition, StudioAhistoricState, + StudioEphemeralState, StudioHistoricStateSequenceEditorMarker, } from './types' import {clamp, uniq} from 'lodash-es' @@ -339,6 +340,11 @@ namespace stateEditors { } } export namespace ephemeral { + export function setShowOutline( + showOutline: StudioEphemeralState['showOutline'], + ) { + drafts().ephemeral.showOutline = showOutline + } export namespace projects { export namespace stateByProjectId { export function _ensure(p: ProjectAddress) { @@ -400,6 +406,16 @@ namespace stateEditors { } } export namespace ahistoric { + export function setPinOutline( + pinOutline: StudioAhistoricState['pinOutline'], + ) { + drafts().ahistoric.pinOutline = pinOutline + } + export function setPinDetails( + pinDetails: StudioAhistoricState['pinDetails'], + ) { + drafts().ahistoric.pinDetails = pinDetails + } export function setVisibilityState( visibilityState: StudioAhistoricState['visibilityState'], ) { diff --git a/theatre/studio/src/store/types/ahistoric.ts b/theatre/studio/src/store/types/ahistoric.ts index 26d68ad..bd2a010 100644 --- a/theatre/studio/src/store/types/ahistoric.ts +++ b/theatre/studio/src/store/types/ahistoric.ts @@ -6,6 +6,14 @@ import type {PointableSet} from '@theatre/shared/utils/PointableSet' import type {StudioSheetItemKey} from '@theatre/shared/utils/ids' export type StudioAhistoricState = { + /** + * undefined means the outline menu is pinned + */ + pinOutline?: boolean + /** + * undefined means the detail panel is pinned + */ + pinDetails?: boolean visibilityState: 'everythingIsHidden' | 'everythingIsVisible' clipboard?: { keyframes?: Keyframe[] diff --git a/theatre/studio/src/store/types/ephemeral.ts b/theatre/studio/src/store/types/ephemeral.ts index 5589c28..8aa3a90 100644 --- a/theatre/studio/src/store/types/ephemeral.ts +++ b/theatre/studio/src/store/types/ephemeral.ts @@ -5,6 +5,26 @@ import type { PaneClassDefinition, } from '@theatre/studio/TheatreStudio' +/** + * Technically, all parts of the ephemeral state can be implemented + * outside the store, using simple Box|Atom of dataverse. + * + * The only reason that _some_ of these cases reside in StudioEphemeralState, + * is to bring them into attention, because these pieces of the state are useful + * in several (3+) places in the application. + * + * Note: Should we just implement all of ephemeral state as boxes and atoms, + * and remove ephemeral state from the store? + * - We'd still have to namespace and organize these pieces of ephemeral state, + * so they're discoverable. + * + * Disadvantage of that: + * - We may want to send over the wire pieces the ephemeral state that other users + * have interest in. For example, if Alice is dragging Planet.position, Bob would + * want to observe the drag, and not just its final state, which would be in the historic + * state. (still, ephemeral state would never be persisted, but parts of it could be sent + * over the wire). + */ export type StudioEphemeralState = { initialised: boolean coreByProject: {[projectId in string]: ProjectState['ephemeral']} @@ -35,4 +55,5 @@ export type StudioEphemeralState = { } } } + showOutline: boolean } diff --git a/theatre/studio/src/toolbars/GlobalToolbar/GlobalToolbar.tsx b/theatre/studio/src/toolbars/ExtensionToolbar/ExtensionToolbar.tsx similarity index 79% rename from theatre/studio/src/toolbars/GlobalToolbar/GlobalToolbar.tsx rename to theatre/studio/src/toolbars/ExtensionToolbar/ExtensionToolbar.tsx index b09e2ef..08d126f 100644 --- a/theatre/studio/src/toolbars/GlobalToolbar/GlobalToolbar.tsx +++ b/theatre/studio/src/toolbars/ExtensionToolbar/ExtensionToolbar.tsx @@ -3,21 +3,15 @@ import {useVal} from '@theatre/react' import type {IExtension} from '@theatre/studio' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import getStudio from '@theatre/studio/getStudio' -import {panelZIndexes} from '@theatre/studio/panels/BasePanel/common' import type {ToolsetConfig} from '@theatre/studio/TheatreStudio' import React, {useLayoutEffect, useMemo} from 'react' + import styled from 'styled-components' import Toolset from './Toolset' const Container = styled.div` - position: fixed; - z-index: ${panelZIndexes.toolbar}; - - top: 12px; - right: 12px; - left: 12px; height: 36px; - pointer-events: none; + /* pointer-events: none; */ display: flex; gap: 1rem; @@ -60,17 +54,9 @@ const ExtensionToolsetRender: React.FC<{ return } -const GlobalToolbar: React.FC<{}> = (props) => { - return ( - - - - - - ) -} - -export const Toolbar: React.FC<{toolbarId: string}> = ({toolbarId}) => { +export const ExtensionToolbar: React.FC<{toolbarId: string}> = ({ + toolbarId, +}) => { const groups: Array = [] const extensionsById = useVal(getStudio().atomP.ephemeral.extensions.byId) @@ -87,7 +73,8 @@ export const Toolbar: React.FC<{toolbarId: string}> = ({toolbarId}) => { } if (groups.length === 0) return null - return <>{groups} + + return {groups} } -export default GlobalToolbar +export default ExtensionToolbar diff --git a/theatre/studio/src/toolbars/GlobalToolbar/Toolset.tsx b/theatre/studio/src/toolbars/ExtensionToolbar/Toolset.tsx similarity index 100% rename from theatre/studio/src/toolbars/GlobalToolbar/Toolset.tsx rename to theatre/studio/src/toolbars/ExtensionToolbar/Toolset.tsx diff --git a/theatre/studio/src/toolbars/GlobalToolbar/tools/IconButton.tsx b/theatre/studio/src/toolbars/ExtensionToolbar/tools/IconButton.tsx similarity index 100% rename from theatre/studio/src/toolbars/GlobalToolbar/tools/IconButton.tsx rename to theatre/studio/src/toolbars/ExtensionToolbar/tools/IconButton.tsx diff --git a/theatre/studio/src/toolbars/GlobalToolbar/tools/Switch.tsx b/theatre/studio/src/toolbars/ExtensionToolbar/tools/Switch.tsx similarity index 100% rename from theatre/studio/src/toolbars/GlobalToolbar/tools/Switch.tsx rename to theatre/studio/src/toolbars/ExtensionToolbar/tools/Switch.tsx diff --git a/theatre/studio/src/toolbars/GlobalToolbar.tsx b/theatre/studio/src/toolbars/GlobalToolbar.tsx new file mode 100644 index 0000000..2185bbb --- /dev/null +++ b/theatre/studio/src/toolbars/GlobalToolbar.tsx @@ -0,0 +1,130 @@ +import {usePrism, useVal} from '@theatre/react' +import getStudio from '@theatre/studio/getStudio' +import React from 'react' +import styled from 'styled-components' +import type {$IntentionalAny} from '@theatre/dataverse/dist/types' +import useTooltip from '@theatre/studio/uiComponents/Popover/useTooltip' +import ErrorTooltip from '@theatre/studio/uiComponents/Popover/ErrorTooltip' +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, + Outline, +} from '@theatre/studio/uiComponents/icons' +import {shouldShowDetailD} from '@theatre/studio/panels/DetailPanel/DetailPanel' + +const Container = styled.div` + height: 36px; + pointer-events: none; + + display: flex; + justify-content: space-between; + padding: 12px; +` + +const NumberOfConflictsIndicator = styled.div` + color: white; + width: 14px; + height: 14px; + background: #d00; + border-radius: 4px; + text-align: center; + line-height: 14px; + font-weight: 600; + font-size: 8px; + position: relative; + left: -6px; + top: -11px; + margin-right: -14px; + box-shadow: 0 4px 6px -4px #00000059; +` + +const SubContainer = styled.div` + display: flex; + gap: 8px; +` + +const GlobalToolbar: React.FC = () => { + const conflicts = usePrism(() => { + const ephemeralStateOfAllProjects = val( + getStudio().atomP.ephemeral.coreByProject, + ) + return Object.entries(ephemeralStateOfAllProjects) + .map(([projectId, state]) => ({projectId, state})) + .filter( + ({state}) => + state.loadingState.type === 'browserStateIsNotBasedOnDiskState', + ) + }, []) + const [triggerTooltip, triggerButtonRef] = useTooltip( + {enabled: conflicts.length > 0, enterDelay: conflicts.length > 0 ? 0 : 200}, + () => + conflicts.length > 0 ? ( + + {conflicts.length === 1 + ? `There is a state conflict in project "${conflicts[0].projectId}". Select the project in the outline below in order to fix it.` + : `There are ${conflicts.length} projects that have state conflicts. They are highlighted in the outline below. `} + + ) : ( + Outline + ), + ) + + 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) + + return ( + + + {triggerTooltip} + { + getStudio().transaction(({stateEditors, drafts}) => { + stateEditors.studio.ahistoric.setPinOutline( + !drafts.ahistoric.pinOutline, + ) + }) + }} + icon={} + pinHintIcon={} + unpinHintIcon={} + pinned={outlinePinned} + hint={showOutline} + /> + {conflicts.length > 0 ? ( + + {conflicts.length} + + ) : null} + + + + { + getStudio().transaction(({stateEditors, drafts}) => { + stateEditors.studio.ahistoric.setPinDetails( + !drafts.ahistoric.pinDetails, + ) + }) + }} + icon={
} + pinHintIcon={} + unpinHintIcon={} + pinned={detailsPinned} + hint={showDetails} + /> + + + ) +} + +export default GlobalToolbar diff --git a/theatre/studio/src/toolbars/PinButton.tsx b/theatre/studio/src/toolbars/PinButton.tsx new file mode 100644 index 0000000..a73fc02 --- /dev/null +++ b/theatre/studio/src/toolbars/PinButton.tsx @@ -0,0 +1,69 @@ +import styled from 'styled-components' +import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' +import type {ComponentPropsWithRef, ReactNode} from 'react'; +import React, { forwardRef} from 'react' + +const Container = styled.button<{pinned?: boolean}>` + ${pointerEventsAutoInNormalMode}; + position: relative; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + font-weight: 600; + width: 32px; + height: 32px; + outline: none; + + color: #a8a8a9; + + background: ${({pinned}) => + pinned ? 'rgba(40, 43, 47, 0.9)' : 'rgba(40, 43, 47, 0.45)'}; + backdrop-filter: blur(14px); + border: none; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + + &:hover { + background: rgba(59, 63, 69, 0.8); + } + + &:active { + 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); + } + } +` + +interface PinButtonProps extends ComponentPropsWithRef<'button'> { + icon: ReactNode + pinHintIcon: ReactNode + unpinHintIcon: ReactNode + hint?: boolean + pinned?: boolean +} + +const PinButton = forwardRef( + ({hint, pinned, icon, pinHintIcon, unpinHintIcon, ...props}, ref) => { + return ( + + {hint && !pinned ? pinHintIcon : hint && pinned ? unpinHintIcon : icon} + + ) + }, +) + +export default PinButton diff --git a/theatre/studio/src/uiComponents/icons/ArrowClockwise.tsx b/theatre/studio/src/uiComponents/icons/ArrowClockwise.tsx new file mode 100644 index 0000000..bc196c4 --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/ArrowClockwise.tsx @@ -0,0 +1,22 @@ +import * as React from 'react' + +function ArrowClockwise(props: React.SVGProps) { + return ( + + + + ) +} + +export default ArrowClockwise diff --git a/theatre/studio/src/uiComponents/icons/ArrowsOutCardinal.tsx b/theatre/studio/src/uiComponents/icons/ArrowsOutCardinal.tsx new file mode 100644 index 0000000..0cb0ee9 --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/ArrowsOutCardinal.tsx @@ -0,0 +1,22 @@ +import * as React from 'react' + +function ArrowsOutCardinal(props: React.SVGProps) { + return ( + + + + ) +} + +export default ArrowsOutCardinal diff --git a/theatre/studio/src/uiComponents/icons/Camera.tsx b/theatre/studio/src/uiComponents/icons/Camera.tsx new file mode 100644 index 0000000..db6f244 --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/Camera.tsx @@ -0,0 +1,31 @@ +import * as React from 'react' + +function Camera(props: React.SVGProps) { + return ( + + + + + ) +} + +export default Camera diff --git a/theatre/studio/src/uiComponents/icons/ChevronDown.tsx b/theatre/studio/src/uiComponents/icons/ChevronDown.tsx new file mode 100644 index 0000000..b2843cc --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/ChevronDown.tsx @@ -0,0 +1,24 @@ +import * as React from 'react' + +function ChevronDown(props: React.SVGProps) { + return ( + + + + ) +} + +export default ChevronDown diff --git a/theatre/studio/src/uiComponents/icons/ChevronLeft.tsx b/theatre/studio/src/uiComponents/icons/ChevronLeft.tsx new file mode 100644 index 0000000..6472fb9 --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/ChevronLeft.tsx @@ -0,0 +1,22 @@ +import * as React from 'react' + +function ChevronLeft(props: React.SVGProps) { + return ( + + + + ) +} + +export default ChevronLeft diff --git a/theatre/studio/src/uiComponents/icons/ChevronRight.tsx b/theatre/studio/src/uiComponents/icons/ChevronRight.tsx new file mode 100644 index 0000000..ec5b38f --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/ChevronRight.tsx @@ -0,0 +1,22 @@ +import * as React from 'react' + +function ChevronRight(props: React.SVGProps) { + return ( + + + + ) +} + +export default ChevronRight diff --git a/theatre/studio/src/uiComponents/icons/Cube.tsx b/theatre/studio/src/uiComponents/icons/Cube.tsx new file mode 100644 index 0000000..610e43d --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/Cube.tsx @@ -0,0 +1,24 @@ +import * as React from 'react' + +function Cube(props: React.SVGProps) { + return ( + + + + ) +} + +export default Cube diff --git a/theatre/studio/src/uiComponents/icons/CubeFull.tsx b/theatre/studio/src/uiComponents/icons/CubeFull.tsx new file mode 100644 index 0000000..28d738d --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/CubeFull.tsx @@ -0,0 +1,22 @@ +import * as React from 'react' + +function CubeFull(props: React.SVGProps) { + return ( + + + + ) +} + +export default CubeFull diff --git a/theatre/studio/src/uiComponents/icons/CubeHalf.tsx b/theatre/studio/src/uiComponents/icons/CubeHalf.tsx new file mode 100644 index 0000000..e030654 --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/CubeHalf.tsx @@ -0,0 +1,22 @@ +import * as React from 'react' + +function CubeHalf(props: React.SVGProps) { + return ( + + + + ) +} + +export default CubeHalf diff --git a/theatre/studio/src/uiComponents/icons/CubeRendered.tsx b/theatre/studio/src/uiComponents/icons/CubeRendered.tsx new file mode 100644 index 0000000..c6577a8 --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/CubeRendered.tsx @@ -0,0 +1,29 @@ +import * as React from 'react' + +function CubeRendered(props: React.SVGProps) { + return ( + + + + + ) +} + +export default CubeRendered diff --git a/theatre/studio/src/uiComponents/icons/Details.tsx b/theatre/studio/src/uiComponents/icons/Details.tsx new file mode 100644 index 0000000..eccce15 --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/Details.tsx @@ -0,0 +1,23 @@ +import * as React from 'react' + +function Details(props: React.SVGProps) { + return ( + + + + ) +} + +export default Details diff --git a/theatre/studio/src/uiComponents/icons/Ellipsis.tsx b/theatre/studio/src/uiComponents/icons/Ellipsis.tsx new file mode 100644 index 0000000..1530873 --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/Ellipsis.tsx @@ -0,0 +1,24 @@ +import * as React from 'react' + +function Ellipsis(props: React.SVGProps) { + return ( + + + + ) +} + +export default Ellipsis diff --git a/theatre/studio/src/uiComponents/icons/GlobeSimple.tsx b/theatre/studio/src/uiComponents/icons/GlobeSimple.tsx new file mode 100644 index 0000000..cb29324 --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/GlobeSimple.tsx @@ -0,0 +1,24 @@ +import * as React from 'react' + +function GlobeSimple(props: React.SVGProps) { + return ( + + + + ) +} + +export default GlobeSimple diff --git a/theatre/studio/src/uiComponents/icons/Outline.tsx b/theatre/studio/src/uiComponents/icons/Outline.tsx new file mode 100644 index 0000000..f7344b5 --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/Outline.tsx @@ -0,0 +1,23 @@ +import * as React from 'react' + +function Outline(props: React.SVGProps) { + return ( + + + + ) +} + +export default Outline diff --git a/theatre/studio/src/uiComponents/icons/Package.tsx b/theatre/studio/src/uiComponents/icons/Package.tsx new file mode 100644 index 0000000..4f38323 --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/Package.tsx @@ -0,0 +1,22 @@ +import * as React from 'react' + +function Package(props: React.SVGProps) { + return ( + + + + ) +} + +export default Package diff --git a/theatre/studio/src/uiComponents/icons/Resize.tsx b/theatre/studio/src/uiComponents/icons/Resize.tsx new file mode 100644 index 0000000..4f045d2 --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/Resize.tsx @@ -0,0 +1,29 @@ +import * as React from 'react' + +function Resize(props: React.SVGProps) { + return ( + + + + + ) +} + +export default Resize diff --git a/theatre/studio/src/uiComponents/icons/index.ts b/theatre/studio/src/uiComponents/icons/index.ts new file mode 100644 index 0000000..8f61bb1 --- /dev/null +++ b/theatre/studio/src/uiComponents/icons/index.ts @@ -0,0 +1,16 @@ +export {default as Outline} from './Outline' +export {default as ArrowClockwise} from './ArrowClockwise' +export {default as ArrowsOutCardinal} from './ArrowsOutCardinal' +export {default as Camera} from './Camera' +export {default as ChevronDown} from './ChevronDown' +export {default as ChevronRight} from './ChevronRight' +export {default as ChevronLeft} from './ChevronLeft' +export {default as Cube} from './Cube' +export {default as CubeFull} from './CubeFull' +export {default as CubeHalf} from './CubeHalf' +export {default as CubeRendered} from './CubeRendered' +export {default as Details} from './Details' +export {default as Ellipsis} from './Ellipsis' +export {default as GlobeSimple} from './GlobeSimple' +export {default as Resize} from './Resize' +export {default as Package} from './Package' diff --git a/theatre/studio/src/uiComponents/toolbar/ToolbarIconButton.tsx b/theatre/studio/src/uiComponents/toolbar/ToolbarIconButton.tsx index fb2a17d..eb8ad56 100644 --- a/theatre/studio/src/uiComponents/toolbar/ToolbarIconButton.tsx +++ b/theatre/studio/src/uiComponents/toolbar/ToolbarIconButton.tsx @@ -5,6 +5,7 @@ import type {$FixMe, $IntentionalAny} from '@theatre/shared/utils/types' import useTooltip from '@theatre/studio/uiComponents/Popover/useTooltip' import mergeRefs from 'react-merge-refs' import MinimalTooltip from '@theatre/studio/uiComponents/Popover/MinimalTooltip' +import ToolbarSwitchSelectContainer from './ToolbarSwitchSelectContainer' const Container = styled.button` ${pointerEventsAutoInNormalMode}; @@ -14,45 +15,65 @@ const Container = styled.button` justify-content: center; font-size: 14px; font-weight: 600; - width: 24px; - height: 24px; + width: 32px; + height: 32px; outline: none; - color: rgba(255, 255, 255, 0.75); - background-color: rgb(47, 49, 53); + color: #a8a8a9; + + background: rgba(40, 43, 47, 0.45); + backdrop-filter: blur(14px); + border: none; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + + filter: drop-shadow(0px 1px 1px rgba(0, 0, 0, 0.25)) + drop-shadow(0px 2px 6px rgba(0, 0, 0, 0.15)); &:hover { - color: #fff; + background: rgba(59, 63, 69, 0.8); + } - background-color: rgba(28, 30, 32, 0.95); - &:after { - border-color: rgba(90, 90, 90, 1); - } + &:active { + background: rgba(82, 88, 96, 0.8); } &.selected { - color: #fff; + background: rgba(40, 43, 47, 0.9); + color: white; + } - background-color: rgba(17, 18, 20, 0.95); - &:after { - border-color: rgb(43, 43, 43); + &:first-child { + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; + } + + &:last-child { + border-bottom-right-radius: 2px; + border-top-right-radius: 2px; + } + + // Don't blur if in a button group, because it already blurs. We need to blur + // on the group-level, otherwise we get seams. + ${ToolbarSwitchSelectContainer} > & { + backdrop-filter: none; + filter: none; + } + + @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); } } - - &:before { - border: 1px solid rgb(62, 62, 62); - position: absolute; - inset: -1px; - display: block; - content: ' '; - z-index: -1; - pointer-events: none; - border-radius: 2px; - box-sizing: border-box; - box-shadow: 0px 3px 4px -3px rgba(0, 0, 0, 0.49); - } - - border: 0; ` const ToolbarIconButton: typeof Container = React.forwardRef( diff --git a/theatre/studio/src/uiComponents/toolbar/ToolbarSwitchSelect.tsx b/theatre/studio/src/uiComponents/toolbar/ToolbarSwitchSelect.tsx index 78100ca..187754f 100644 --- a/theatre/studio/src/uiComponents/toolbar/ToolbarSwitchSelect.tsx +++ b/theatre/studio/src/uiComponents/toolbar/ToolbarSwitchSelect.tsx @@ -1,11 +1,9 @@ import type {ReactElement} from 'react' import React from 'react' import type {IconType} from 'react-icons' -import {Group, Button} from 'reakit' -import styled from 'styled-components' +import {Button} from 'reakit' import ButtonImpl from './ToolbarIconButton' - -const Opt = styled(ButtonImpl)`` +import Container from './ToolbarSwitchSelectContainer' function OptionButton({ value, @@ -22,7 +20,7 @@ function OptionButton({ }) { return ( <> - ({ title={label} > {icon} - + ) } @@ -45,11 +43,6 @@ interface Props