Skip to content

Commit

Permalink
feat(desk-tool): add updated ReviewChangesButton
Browse files Browse the repository at this point in the history
  • Loading branch information
hermanwikner authored and bjoerge committed Jan 6, 2022
1 parent a673a56 commit 53c3233
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 64 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import React, {ComponentProps} from 'react'
import {motion} from 'framer-motion'
import styled, {keyframes} from 'styled-components'

const StyledMotionPath = styled(motion.path)`
transform-origin: center;
`

type MotionCircleProps = Omit<ComponentProps<typeof motion.circle>, 'd'>
type MotionPathProps = Omit<ComponentProps<typeof motion.path>, 'd'>

const Circle = (props: MotionCircleProps) => (
<motion.circle fill="none" r="8" cx="12.5" cy="12.5" strokeWidth="1.2" {...props} />
)
const Arrows = (props: MotionPathProps) => (
<StyledMotionPath
fill="none"
d="M14 17.5619L11.5 20.5L14.5 23.0619M11 7.43811L13.5 4.50001L10.5 1.93811"
{...props}
/>
)
const Checkmark = (props: MotionPathProps) => (
<motion.path d="M9.5 12.1316L11.7414 14.5L16 10" {...props} />
)
const Edit = (props: MotionPathProps) => (
<motion.path d="M15 7L18 10M6 19L7 15L17 5L20 8L10 18L6 19Z" {...props} />
)

const rotateAnimation = keyframes`
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
`
const RotateGroup = styled.g`
transform-origin: center;
&[data-rotate] {
animation: ${rotateAnimation} 1s ease-in-out infinite;
}
`

const root = {
syncing: {
scale: 1,
transition: {
duration: 0,
},
},
saved: {
scale: [1, 0.8, 1.2, 0.9, 1.1, 0.95, 1.05, 0.99, 1],
transition: {
duration: 0.5,
delay: 0.2,
},
},
changes: {transition: {duration: 0}},
}

const circle = {
syncing: {
strokeDasharray: '0, 0, 23, 3, 23, 3',
strokeDashoffset: '10',
opacity: 1,
transition: {
duration: 0,
},
},
saved: {
strokeDasharray: '0, 0, 23, 0, 23, 0',
strokeDashoffset: '10',
opacity: 1,
transition: {
duration: 0.2,
},
},
changes: {
strokeDasharray: '0, 60, 23, 0, 23, 0',
strokeDashoffset: '0',
opacity: 0,
transition: {
duration: 0.5,
},
},
}

const arrows = {
syncing: {
opacity: 1,
transition: {
duration: 0,
},
},
saved: {
opacity: 0,
transition: {
duration: 0.2,
},
},
changes: {
opacity: 0,
},
}

const checkmark = {
syncing: {
pathLength: 0,
transition: {duration: 0},
},
saved: {
pathLength: 1,
transition: {
delay: 0.4,
duration: 0.3,
},
},
changes: {
pathLength: 0,
transition: {
duration: 0.2,
},
},
}

const edit = {
syncing: {
pathLength: 0,
transition: {duration: 0},
},
saved: {
pathLength: 0,
transition: {duration: 0},
},
changes: {
pathLength: 1,
transition: {
duration: 0.4,
delay: 0.5,
},
},
}

interface AnimatedStatusIconProps {
status?: 'changes' | 'saved' | 'syncing'
}

export function AnimatedStatusIcon(props: AnimatedStatusIconProps) {
const {status} = props

if (!status) {
return null
}

return (
<svg
width="1em"
height="1em"
viewBox="0 0 25 25"
fill="none"
stroke="currentColor"
strokeWidth="1.2"
data-sanity-icon=""
>
<motion.g variants={root} initial={status} animate={status}>
<RotateGroup data-rotate={status === 'changes' ? undefined : ''}>
<Arrows variants={arrows} initial={status} animate={status} />
<Circle variants={circle} initial={status} animate={status} />
</RotateGroup>
<Checkmark variants={checkmark} initial={status} animate={status} />
<Edit variants={edit} initial={status} animate={status} />
</motion.g>
</svg>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React, {useMemo} from 'react'
import {Box, Button, ButtonProps, Flex, Stack, Text, Tooltip} from '@sanity/ui'
import {useTimeAgo} from '@sanity/base/hooks'
import {AnimatedStatusIcon} from './AnimatedStatusIcon'

interface ReviewChangesButtonProps extends React.HTMLProps<HTMLButtonElement> {
status?: 'changes' | 'saved' | 'syncing'
lastUpdated?: string
collapsed?: boolean
}

const ReviewButton = React.forwardRef(function ReviewButton(
props: ReviewChangesButtonProps & ButtonProps,
ref: React.ForwardedRef<HTMLButtonElement>
) {
const {collapsed, status, lastUpdated, ...rest} = props
const lastUpdatedTime = useTimeAgo(lastUpdated || '', {minimal: true})
const lastUpdatedTimeAgo = useTimeAgo(lastUpdated || '', {minimal: true, agoSuffix: true})

const buttonProps: ButtonProps = useMemo(() => {
if (status === 'syncing') {
return {
text: 'Saving...',
tone: undefined,
}
}
if (status === 'changes') {
return {
text: lastUpdatedTime,
tone: 'caution',
}
}
if (status === 'saved') {
return {
text: 'Saved!',
tone: 'positive',
}
}

return {}
}, [status, lastUpdatedTime])

if (!status) {
return null
}

return (
<Tooltip
portal
disabled={status !== 'changes'}
content={
<Stack padding={3} space={3}>
<Text size={1} weight="semibold">
Review changes
</Text>
<Text size={1} muted>
Changes saved {lastUpdatedTimeAgo}
</Text>
</Stack>
}
>
<Button mode="bleed" justify="flex-start" tone={buttonProps?.tone} {...rest} ref={ref}>
<Flex align="center">
<Box marginRight={collapsed ? 0 : 3}>
<Text>
<AnimatedStatusIcon status={status} />
</Text>
</Box>
{!collapsed && (
<Text size={1} weight="medium">
{buttonProps?.text}
</Text>
)}
</Flex>
</Button>
</Tooltip>
)
})

export const ReviewChangesButton = React.memo(ReviewButton)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ReviewChangesButton'

0 comments on commit 53c3233

Please sign in to comment.