diff --git a/panda.config.ts b/panda.config.ts index 052abb023b..3f5a5c2d0e 100644 --- a/panda.config.ts +++ b/panda.config.ts @@ -119,6 +119,17 @@ const keyframes = defineKeyframes({ fill: 'white', }, }, + bobble: { + '0%': { + transform: 'translateX(-50%) translateY(0)', + }, + '50%': { + transform: 'translateX(-50%) translateY(10px)', + }, + '100%': { + transform: 'translateX(-50%) translateY(0)', + }, + }, }) const globalCss = defineGlobalStyles({ @@ -294,6 +305,7 @@ export default defineConfig({ }, zIndex: { popup: { value: 1500 }, + hoverArrow: { value: 1400 }, gestureTrace: { value: 50 }, commandPalette: { value: 45 }, modal: { value: 40 }, diff --git a/src/actions/index.ts b/src/actions/index.ts index 4a2f597518..99f3a7ffc3 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -107,3 +107,4 @@ export { default as undoArchive } from './undoArchive' export { default as unknownAction } from './unknownAction' export { default as updateSplitPosition } from './updateSplitPosition' export { default as updateThoughts } from './updateThoughts' +export { default as updateHoveringPath } from './updateHoveringPath' diff --git a/src/actions/updateHoveringPath.ts b/src/actions/updateHoveringPath.ts new file mode 100644 index 0000000000..63e31add16 --- /dev/null +++ b/src/actions/updateHoveringPath.ts @@ -0,0 +1,18 @@ +import _ from 'lodash' +import Path from '../@types/Path' +import State from '../@types/State' +import Thunk from '../@types/Thunk' + +/** Reducer for setting the hoveringPath when drag leave is firing. */ +const updateHoveringPath = (state: State, { path }: { path: Path | undefined }) => ({ + ...state, + hoveringPath: path, +}) + +/** Action-creator for state.hoveringPath. */ +export const updateHoveringPathActionCreator = + ({ path }: { path: Path | undefined }): Thunk => + dispatch => + dispatch([{ type: 'updateHoveringPath', path }]) + +export default _.curryRight(updateHoveringPath) diff --git a/src/components/HoverArrow.tsx b/src/components/HoverArrow.tsx new file mode 100644 index 0000000000..5dd4efe280 --- /dev/null +++ b/src/components/HoverArrow.tsx @@ -0,0 +1,40 @@ +import { css } from '../../styled-system/css' + +/** Renders upward/downward arrow when hovering over a sorted context. */ +const HoverArrow = ({ + hoverArrowVisibility, + top, + bottom, +}: { + hoverArrowVisibility: 'above' | 'below' | null + /** The distance the arrow is rendered from the top of the document (px) if hoverArrowVisibility is 'above'. */ + top: number + /** The distance the arrow is rendered from the bottom of the document (px) if hoverArrowVisibility is 'below'. */ + bottom: number +}) => { + return ( + hoverArrowVisibility && ( +
+ ) + ) +} + +export default HoverArrow diff --git a/src/components/LayoutTree.tsx b/src/components/LayoutTree.tsx index 7ac78ade57..683d966bde 100644 --- a/src/components/LayoutTree.tsx +++ b/src/components/LayoutTree.tsx @@ -13,6 +13,7 @@ import ThoughtId from '../@types/ThoughtId' import { isTouch } from '../browser' import { HOME_PATH } from '../constants' import testFlags from '../e2e/testFlags' +import useSortedContext from '../hooks/useSortedContext' import attributeEquals from '../selectors/attributeEquals' import calculateAutofocus from '../selectors/calculateAutofocus' import findDescendant from '../selectors/findDescendant' @@ -38,6 +39,7 @@ import parseLet from '../util/parseLet' import safeRefMerge from '../util/safeRefMerge' import unroot from '../util/unroot' import DropEnd from './DropEnd' +import HoverArrow from './HoverArrow' import VirtualThought from './VirtualThought' /** 1st Pass: A thought with rendering information after the tree has been linearized. */ @@ -51,6 +53,7 @@ type TreeThought = { // index among all visible thoughts in the tree indexDescendant: number isCursor: boolean + isInSortedContext: boolean isTableCol1: boolean isTableCol2: boolean isTableCol2Child: boolean @@ -58,6 +61,7 @@ type TreeThought = { leaf: boolean path: Path prevChild: Thought + rank: number showContexts?: boolean simplePath: SimplePath // style inherited from parents with =children/=style and grandparents with =grandchildren/=style @@ -186,24 +190,27 @@ const useNavAndFooterHeight = () => { // Get the nav and footer heights for the spaceBelow calculation. // Nav hight changes when the breadcrumbs wrap onto multiple lines. // Footer height changes on font size change. - const [navAndFooterHeight, setNavAndFooterHeight] = useState(0) + const [navbarHeight, setNavbarHeight] = useState(0) + const [footerHeight, setFooterHeight] = useState(0) // Read the footer and nav heights on render and set the refs so that the spaceBelow calculation is updated on the next render. // This works because there is always a second render due to useSizeTracking. // No risk of infinite render since the effect cannot change the height of the nav or footer. - // nav/footer height -> effect -> setNavAndFooterHeight -> render -> effect -> setNavAndFooterHeight (same values) + // nav/footer height -> effect -> setNavbarHeight/setFooterHeight -> render -> effect -> setNavbarHeight/setFooterHeight (same values) // eslint-disable-next-line react-hooks/exhaustive-deps useEffect( _.throttle(() => { const navEl = document.querySelector('[aria-label="nav"]') const footerEl = document.querySelector('[aria-label="footer"]') - setNavAndFooterHeight( - (navEl?.getBoundingClientRect().height || 0) + (footerEl?.getBoundingClientRect().height || 0), - ) + setNavbarHeight(navEl?.getBoundingClientRect().height || 0) + setFooterHeight(footerEl?.getBoundingClientRect().height || 0) }, 16.666), ) - return navAndFooterHeight + return { + navbarHeight, + footerHeight, + } } /** Recursiveley calculates the tree of visible thoughts, in order, represented as a flat list of thoughts with tree layout information. */ @@ -290,6 +297,7 @@ const linearizeTree = ( const isTable = attributeEquals(state, child.id, '=view', 'Table') const isTableCol1 = attributeEquals(state, head(simplePath), '=view', 'Table') + const isInSortedContext = attributeEquals(state, head(simplePath), '=sort', 'Alphabetical') const isTableCol2 = attributeEquals(state, head(rootedParentOf(state, simplePath)), '=view', 'Table') const isTableCol2Child = attributeEquals(state, head(rootedParentOf(state, parentOf(simplePath))), '=view', 'Table') const autofocus = calculateAutofocus(state, childPath) @@ -301,6 +309,7 @@ const linearizeTree = ( indexChild: i, indexDescendant: virtualIndexNew, isCursor, + isInSortedContext, isTableCol1, isTableCol2, isTableCol2Child, @@ -313,6 +322,7 @@ const linearizeTree = ( leaf: !hasChildren(state, filteredChild.id), path: childPath, prevChild: filteredChildren[i - 1], + rank: child.rank, showContexts: contextViewActive, simplePath: contextViewActive ? thoughtToPath(state, child.id) : appendToPathMemo(simplePath, child.id), style, @@ -364,14 +374,25 @@ const LayoutTree = () => { state.cursor.length - (hasChildren(state, head(state.cursor)) ? 2 : 3) : 0, ) + const scrollTop = scrollTopStore.useState() - const [bulletWidth, setBulletWidth] = useState