summaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to 'components')
-rw-r--r--components/Sidebar/Collapse.tsx146
-rw-r--r--components/Sidebar/transition-utils.tsx144
-rw-r--r--components/contextmenu.tsx4
3 files changed, 291 insertions, 3 deletions
diff --git a/components/Sidebar/Collapse.tsx b/components/Sidebar/Collapse.tsx
new file mode 100644
index 0000000..b297605
--- /dev/null
+++ b/components/Sidebar/Collapse.tsx
@@ -0,0 +1,146 @@
+// modified from https://github.com/chakra-ui/chakra-ui/blob/fc3b97d0978cf2adb9fc79157c6e42b4b68155c5/packages/transition/src/collapse.tsx
+
+import { cx, mergeWith, warn, __DEV__ } from '@chakra-ui/utils'
+import { AnimatePresence, HTMLMotionProps, motion, Variants as _Variants } from 'framer-motion'
+import * as React from 'react'
+import { TransitionEasings, Variants, withDelay, WithTransitionConfig } from './transition-utils'
+
+const isNumeric = (value?: string | number) => value != null && parseInt(value.toString(), 10) > 0
+
+export interface CollapseOptions {
+ /**
+ * If `true`, the opacity of the content will be animated
+ * @default true
+ */
+ animateOpacity?: boolean
+ /**
+ * The size you want the content in its collapsed state.
+ * @default 0
+ */
+ startingSize?: number | string
+ /**
+ * The size you want the content in its expanded state.
+ * @default "auto"
+ */
+ endingSize?: number | string
+ /**
+ * The dimension you want to collapse by.
+ * @default "size"
+ */
+ dimension?: string
+}
+
+const defaultTransitions = {
+ exit: {
+ size: { duration: 0.2, ease: TransitionEasings.ease },
+ opacity: { duration: 0.3, ease: TransitionEasings.ease },
+ },
+ enter: {
+ size: { duration: 0.3, ease: TransitionEasings.ease },
+ opacity: { duration: 0.4, ease: TransitionEasings.ease },
+ },
+}
+
+const variants: Variants<CollapseOptions> = {
+ exit: ({ animateOpacity, startingSize, transition, transitionEnd, delay, dimension }) => ({
+ ...(animateOpacity && { opacity: isNumeric(startingSize) ? 1 : 0 }),
+ overflow: 'hidden',
+ [dimension as string]: startingSize,
+ transitionEnd: transitionEnd?.exit,
+ transition: transition?.exit ?? withDelay.exit(defaultTransitions.exit, delay),
+ }),
+ enter: ({ animateOpacity, endingSize, transition, transitionEnd, delay, dimension }) => ({
+ ...(animateOpacity && { opacity: 1 }),
+ [dimension as string]: endingSize,
+ transitionEnd: transitionEnd?.enter,
+ transition: transition?.enter ?? withDelay.enter(defaultTransitions.enter, delay),
+ }),
+}
+
+export type ICollapse = CollapseProps
+
+export interface CollapseProps
+ extends WithTransitionConfig<HTMLMotionProps<'div'>>,
+ CollapseOptions {}
+
+export const Collapse = React.forwardRef<HTMLDivElement, CollapseProps>((props, ref) => {
+ const {
+ in: isOpen,
+ unmountOnExit,
+ animateOpacity = true,
+ startingSize = 0,
+ endingSize = 'auto',
+ dimension = 'height',
+ style,
+ className,
+ transition,
+ transitionEnd,
+ ...rest
+ } = props
+
+ const [mounted, setMounted] = React.useState(false)
+ React.useEffect(() => {
+ const timeout = setTimeout(() => {
+ setMounted(true)
+ })
+ return () => clearTimeout(timeout)
+ }, [])
+
+ /**
+ * Warn 🚨: `startingSize` and `unmountOnExit` are mutually exclusive
+ *
+ * If you specify a starting size, the collapsed needs to be mounted
+ * for the size to take effect.
+ */
+ warn({
+ condition: Boolean(startingSize > 0 && unmountOnExit),
+ message: `startingSize and unmountOnExit are mutually exclusive. You can't use them together`,
+ })
+
+ const hasStartingSize = parseFloat(startingSize.toString()) > 0
+
+ const custom = {
+ startingSize,
+ endingSize,
+ animateOpacity,
+ dimension,
+ transition: !mounted ? { enter: { duration: 0 } } : transition,
+ transitionEnd: mergeWith(transitionEnd, {
+ enter: { overflow: 'initial' },
+ exit: unmountOnExit
+ ? undefined
+ : {
+ display: hasStartingSize ? 'block' : 'none',
+ },
+ }),
+ }
+
+ const show = unmountOnExit ? isOpen : true
+ const animate = isOpen || unmountOnExit ? 'enter' : 'exit'
+
+ return (
+ <AnimatePresence initial={false} custom={custom}>
+ {show && (
+ <motion.div
+ ref={ref}
+ {...rest}
+ className={cx('chakra-collapse', className)}
+ style={{
+ overflow: 'hidden',
+ display: 'block',
+ ...style,
+ }}
+ custom={custom}
+ variants={variants as _Variants}
+ initial={unmountOnExit ? 'exit' : false}
+ animate={animate}
+ exit="exit"
+ />
+ )}
+ </AnimatePresence>
+ )
+})
+
+if (__DEV__) {
+ Collapse.displayName = 'Collapse'
+}
diff --git a/components/Sidebar/transition-utils.tsx b/components/Sidebar/transition-utils.tsx
new file mode 100644
index 0000000..68634bb
--- /dev/null
+++ b/components/Sidebar/transition-utils.tsx
@@ -0,0 +1,144 @@
+// Taken from https://github.com/chakra-ui/chakra-ui/blob/fc3b97d0978cf2adb9fc79157c6e42b4b68155c5/packages/transition/src/transition-utils.ts
+import { isNumber } from '@chakra-ui/utils'
+import { Target, TargetAndTransition, Transition } from 'framer-motion'
+
+type TargetResolver<P = {}> = (
+ props: P & {
+ transition?: TransitionConfig
+ transitionEnd?: TransitionEndConfig
+ delay?: number | DelayConfig
+ },
+) => TargetAndTransition
+
+type Variant<P = {}> = TargetAndTransition | TargetResolver<P>
+
+export type Variants<P = {}> = {
+ enter: Variant<P>
+ exit: Variant<P>
+ initial?: Variant<P>
+}
+
+type WithMotionState<P> = Partial<Record<'enter' | 'exit', P>>
+
+export type TransitionConfig = WithMotionState<Transition>
+
+export type TransitionEndConfig = WithMotionState<Target>
+
+export type DelayConfig = WithMotionState<number>
+
+export const TransitionEasings = {
+ ease: [0.25, 0.1, 0.25, 1],
+ easeIn: [0.4, 0, 1, 1],
+ easeOut: [0, 0, 0.2, 1],
+ easeInOut: [0.4, 0, 0.2, 1],
+} as const
+
+export const TransitionVariants = {
+ scale: {
+ enter: { scale: 1 },
+ exit: { scale: 0.95 },
+ },
+ fade: {
+ enter: { opacity: 1 },
+ exit: { opacity: 0 },
+ },
+ pushLeft: {
+ enter: { x: '100%' },
+ exit: { x: '-30%' },
+ },
+ pushRight: {
+ enter: { x: '-100%' },
+ exit: { x: '30%' },
+ },
+ pushUp: {
+ enter: { y: '100%' },
+ exit: { y: '-30%' },
+ },
+ pushDown: {
+ enter: { y: '-100%' },
+ exit: { y: '30%' },
+ },
+ slideLeft: {
+ position: { left: 0, top: 0, bottom: 0, width: '100%' },
+ enter: { x: 0 },
+ exit: { x: '-100%' },
+ },
+ slideRight: {
+ position: { right: 0, top: 0, bottom: 0, width: '100%' },
+ enter: { x: 0 },
+ exit: { x: '100%' },
+ },
+ slideUp: {
+ position: { top: 0, left: 0, right: 0, maxWidth: '100vw' },
+ enter: { y: 0 },
+ exit: { y: '-100%' },
+ },
+ slideDown: {
+ position: { bottom: 0, left: 0, right: 0, maxWidth: '100vw' },
+ enter: { y: 0 },
+ exit: { y: '100%' },
+ },
+}
+
+export type SlideDirection = 'top' | 'left' | 'bottom' | 'right'
+
+export function slideTransition(options?: { direction?: SlideDirection }) {
+ const side = options?.direction ?? 'right'
+ switch (side) {
+ case 'right':
+ return TransitionVariants.slideRight
+ case 'left':
+ return TransitionVariants.slideLeft
+ case 'bottom':
+ return TransitionVariants.slideDown
+ case 'top':
+ return TransitionVariants.slideUp
+ default:
+ return TransitionVariants.slideRight
+ }
+}
+
+export const TransitionDefaults = {
+ enter: {
+ duration: 0.2,
+ ease: TransitionEasings.easeOut,
+ },
+ exit: {
+ duration: 0.1,
+ ease: TransitionEasings.easeIn,
+ },
+} as const
+
+export type WithTransitionConfig<P extends object> = Omit<P, 'transition'> & {
+ /**
+ * If `true`, the element will unmount when `in={false}` and animation is done
+ */
+ unmountOnExit?: boolean
+ /**
+ * Show the component; triggers the enter or exit states
+ */
+ in?: boolean
+ /**
+ * Custom `transition` definition for `enter` and `exit`
+ */
+ transition?: TransitionConfig
+ /**
+ * Custom `transitionEnd` definition for `enter` and `exit`
+ */
+ transitionEnd?: TransitionEndConfig
+ /**
+ * Custom `delay` definition for `enter` and `exit`
+ */
+ delay?: number | DelayConfig
+}
+
+export const withDelay = {
+ enter: (transition: Transition, delay?: number | DelayConfig) => ({
+ ...transition,
+ delay: isNumber(delay) ? delay : delay?.['enter'],
+ }),
+ exit: (transition: Transition, delay?: number | DelayConfig) => ({
+ ...transition,
+ delay: isNumber(delay) ? delay : delay?.['exit'],
+ }),
+}
diff --git a/components/contextmenu.tsx b/components/contextmenu.tsx
index bff4861..118b8bb 100644
--- a/components/contextmenu.tsx
+++ b/components/contextmenu.tsx
@@ -50,13 +50,12 @@ export default interface ContextMenuProps {
background: Boolean
target: OrgRoamNode | null
nodeType?: string
- coordinates: { [direction: string]: number }
+ coordinates: { [direction: string]: number | undefined }
handleLocal: (node: OrgRoamNode, add: string) => void
menuClose: () => void
scope: { nodeIds: string[] }
webSocket: any
setPreviewNode: any
- contextMenuRef: any
}
export const ContextMenu = (props: ContextMenuProps) => {
@@ -70,7 +69,6 @@ export const ContextMenu = (props: ContextMenuProps) => {
scope,
webSocket,
setPreviewNode,
- contextMenuRef,
} = props
const { isOpen, onOpen, onClose } = useDisclosure()
const copyRef = useRef<any>()