fix(advanced checklist): style and layout improvements (#1182)

* fix: reorder icon color

* fix: change reorder icon design

* feat: useResize hook

* feat: responsive task item

* fix: adjust padding for mobile devices

* fix: checkbox size should be proportional to text size

Co-authored-by: Johnny Almonte <johnny243@users.noreply.github.com>
This commit is contained in:
Johnny A
2022-06-29 15:18:54 -04:00
committed by GitHub
parent 38dc61a80e
commit 6c19adba19
16 changed files with 170 additions and 76 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -83,6 +83,7 @@
"@reach/alert-dialog": "0.16.2",
"@reach/menu-button": "0.16.2",
"@reach/visually-hidden": "0.16.0",
"@react-hook/resize-observer": "^1.2.5",
"@reduxjs/toolkit": "1.8.0",
"@standardnotes/editor-kit": "2.2.5",
"@standardnotes/stylekit": "5.23.0",

View File

@@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react'
import useResizeObserver from '@react-hook/resize-observer'
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { AppDispatch, RootState } from './store'
@@ -16,3 +17,18 @@ export const useDidMount = (effect: React.EffectCallback, deps?: React.Dependenc
}
}, [deps, didMount, effect])
}
export const useResize = (ref: React.RefObject<HTMLElement>, effect: (target: HTMLElement) => void) => {
const [size, setSize] = useState<DOMRect>()
function isDeepEqual(prevSize?: DOMRect, nextSize?: DOMRect) {
return JSON.stringify(prevSize) === JSON.stringify(nextSize)
}
useResizeObserver(ref, ({ contentRect, target }) => {
if (!isDeepEqual(size, contentRect)) {
setSize(contentRect)
effect(target as HTMLElement)
}
})
}

View File

@@ -5,7 +5,7 @@ const StyledTextArea = styled.textarea`
background-color: transparent;
border: none;
color: inherit;
font-size: var(--sn-stylekit-font-size-h3);
font-size: 1rem;
font-weight: 400;
margin-left: 6px;
outline: none;

View File

@@ -5,13 +5,13 @@ type ReorderIconProps = {
export const ReorderIcon: React.FC<ReorderIconProps> = ({ highlight = false }) => {
return (
<svg
viewBox="0 0 24 24"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={`sn-icon block ${highlight ? 'info' : ''}`}
className={`sn-icon block ${highlight ? 'info' : 'neutral'}`}
data-testid="reorder-icon"
>
<path d="M3 15H21V13H3V15ZM3 19H21V17H3V19ZM3 11H21V9H3V11ZM3 5V7H21V5H3Z" />
<path d="M17 5V6.66667H3V5H17ZM3 15H17V13.3333H3V15ZM3 10.8333H17V9.16667H3V10.8333Z" />
</svg>
)
}

View File

@@ -35,6 +35,12 @@ $transition-duration: 750ms;
width: 22px;
}
.align-baseline {
.checkbox-button {
top: -10px !important;
}
}
.checkbox-square,
.checkbox-mark {
cursor: pointer;

View File

@@ -20,6 +20,10 @@ const TaskGroupContainer = styled.div<{ isLast?: boolean }>`
box-sizing: border-box;
padding: 16px;
margin-bottom: ${({ isLast }) => (!isLast ? '9px' : '0px')};
@media only screen and (max-width: 600px) {
padding: 8px 10px;
}
`
type CollapsableContainerProps = {

View File

@@ -1,9 +1,9 @@
import './TaskItem.scss'
import { ChangeEvent, createRef, KeyboardEvent, useEffect, useState } from 'react'
import { ChangeEvent, createRef, KeyboardEvent, useState } from 'react'
import styled from 'styled-components'
import { useAppDispatch, useAppSelector, useDidMount } from '../../app/hooks'
import { useAppDispatch, useAppSelector, useDidMount, useResize } from '../../app/hooks'
import { taskDeleted, TaskModel, taskModified, taskToggled } from './tasks-slice'
import { CheckBoxInput, TextAreaInput } from '../../common/components'
@@ -33,7 +33,7 @@ const Container = styled.div<{ completed?: boolean }>`
`}
min-width: 10%;
max-width: 85%;
max-width: 90%;
`
export type TaskItemProps = {
@@ -53,22 +53,29 @@ const TaskItem: React.FC<TaskItemProps> = ({ task, groupName, innerRef, ...props
const [completed, setCompleted] = useState(!!task.completed)
const [description, setDescription] = useState(task.description)
function resizeTextArea(textarea: HTMLTextAreaElement | null): void {
function resizeTextArea(textarea: HTMLElement): void {
if (!textarea) {
return
}
const heightOffset = 4
/**
* Set to 1px first to reset scroll height in case it shrunk.
*/
const heightOffset = 4
textarea.style.height = '1px'
textarea.style.height = textarea.scrollHeight - heightOffset + 'px'
}
useEffect(() => {
resizeTextArea(textAreaRef.current)
})
const singleLineHeight = 20
const currentHeight = parseFloat(textarea.style.height)
if (currentHeight > singleLineHeight) {
textarea.parentElement?.classList.add('align-baseline')
textarea.parentElement?.classList.remove('align-center')
} else {
textarea.parentElement?.classList.add('align-center')
textarea.parentElement?.classList.remove('align-baseline')
}
}
function onCheckBoxToggle() {
const newCompletedState = !completed
@@ -122,6 +129,8 @@ const TaskItem: React.FC<TaskItemProps> = ({ task, groupName, innerRef, ...props
return () => clearTimeout(timeoutId)
}, [description, groupName])
useResize(textAreaRef, resizeTextArea)
return (
<Container data-testid="task-item" completed={completed} ref={innerRef} {...props}>
<CheckBoxInput testId="check-box-input" checked={completed} disabled={!canEdit} onChange={onCheckBoxToggle} />

View File

@@ -26,7 +26,7 @@ const InnerTasksContainer = styled.div<{ collapsed: boolean }>`
flex-direction: column;
& > *:not(:last-child) {
margin-bottom: 5px;
margin-bottom: 7px;
}
`
@@ -94,72 +94,70 @@ const TasksSection: React.FC<TasksSectionProps> = ({ groupName, tasks, section,
ref={provided.innerRef}
>
<TransitionGroup component={null} childFactory={(child) => React.cloneElement(child)}>
{tasks.map((task, index) => {
return (
<CSSTransition
key={`${task.id}-${!!task.completed}`}
classNames={{
enter: 'fade-in',
enterActive: 'fade-in',
enterDone: 'fade-in',
exit: 'fade-out',
exitActive: 'fade-out',
exitDone: 'fade-out',
}}
timeout={{
enter: 1_500,
exit: 1_250,
}}
onEnter={(node: HTMLElement) => {
node.classList.remove('explode')
}}
onEntered={(node: HTMLElement) => {
node.classList.remove('fade-in')
{tasks.map((task, index) => (
<CSSTransition
key={`${task.id}-${!!task.completed}`}
classNames={{
enter: 'fade-in',
enterActive: 'fade-in',
enterDone: 'fade-in',
exit: 'fade-out',
exitActive: 'fade-out',
exitDone: 'fade-out',
}}
timeout={{
enter: 1_500,
exit: 1_250,
}}
onEnter={(node: HTMLElement) => {
node.classList.remove('explode')
}}
onEntered={(node: HTMLElement) => {
node.classList.remove('fade-in')
const completed = !!task.completed
completed && node.classList.add('explode')
const completed = !!task.completed
completed && node.classList.add('explode')
node.addEventListener(
'animationend',
() => {
node.classList.remove('explode')
},
false,
node.addEventListener(
'animationend',
() => {
node.classList.remove('explode')
},
false,
)
}}
onExited={(node: HTMLElement) => {
node.classList.remove('fade-out')
}}
addEndListener={(node, done) => {
done()
}}
mountOnEnter
unmountOnExit
>
<Draggable
key={`draggable-${task.id}`}
draggableId={`draggable-${task.id}`}
index={index}
isDragDisabled={!canEdit}
>
{({ innerRef, draggableProps, dragHandleProps }, { isDragging }) => {
const { style, ...restDraggableProps } = draggableProps
return (
<div className="task-item" style={getItemStyle(isDragging, style)} {...restDraggableProps}>
<TaskItem
key={`task-item-${task.id}`}
task={task}
groupName={groupName}
innerRef={innerRef}
{...dragHandleProps}
/>
</div>
)
}}
onExited={(node: HTMLElement) => {
node.classList.remove('fade-out')
}}
addEndListener={(node, done) => {
done()
}}
mountOnEnter
unmountOnExit
>
<Draggable
key={`draggable-${task.id}`}
draggableId={`draggable-${task.id}`}
index={index}
isDragDisabled={!canEdit}
>
{({ innerRef, draggableProps, dragHandleProps }, { isDragging }) => {
const { style, ...restDraggableProps } = draggableProps
return (
<div className="task-item" style={getItemStyle(isDragging, style)} {...restDraggableProps}>
<TaskItem
key={`task-item-${task.id}`}
task={task}
groupName={groupName}
innerRef={innerRef}
{...dragHandleProps}
/>
</div>
)
}}
</Draggable>
</CSSTransition>
)
})}
</Draggable>
</CSSTransition>
))}
</TransitionGroup>
{provided.placeholder}
</InnerTasksContainer>

View File

@@ -23,6 +23,10 @@ import { getPlainPreview } from './common/utils'
const MainContainer = styled.div`
margin: 16px;
padding-bottom: 60px;
@media only screen and (max-width: 600px) {
margin: 8px;
}
`
const FloatingContainer = styled.div`

View File

@@ -67,6 +67,14 @@ html {
.pt-1px {
padding-top: 1px;
}
.align-baseline {
align-items: baseline;
}
.align-center {
align-items: center;
}
}
:root {

View File

@@ -2995,6 +2995,13 @@ __metadata:
languageName: node
linkType: hard
"@juggle/resize-observer@npm:^3.3.1":
version: 3.3.1
resolution: "@juggle/resize-observer@npm:3.3.1"
checksum: ddabc4044276a2cb57d469c4917206c7e39f2463aa8e3430e33e4eda554412afe29c22afa40e6708b49dad5d56768dc83acd68a704b1dcd49a0906bb96b991b2
languageName: node
linkType: hard
"@leichtgewicht/ip-codec@npm:^2.0.1":
version: 2.0.4
resolution: "@leichtgewicht/ip-codec@npm:2.0.4"
@@ -4425,6 +4432,39 @@ __metadata:
languageName: node
linkType: hard
"@react-hook/latest@npm:^1.0.2":
version: 1.0.3
resolution: "@react-hook/latest@npm:1.0.3"
peerDependencies:
react: ">=16.8"
checksum: 2408c9cd35c5cfa7697b6da3bc5eebef254a932ade70955074c474f23be7dd3e2f81bbba12edcc9208bd0f89c6ed366d6b11d4f6d7b1052877a0bac8f74afad4
languageName: node
linkType: hard
"@react-hook/passive-layout-effect@npm:^1.2.0":
version: 1.2.1
resolution: "@react-hook/passive-layout-effect@npm:1.2.1"
peerDependencies:
react: ">=16.8"
checksum: 217cb8aa90fb8e677672319a9a466d7752890cf4357c76df000b207696e9cc717cf2ee88080671cc9dae238a82f92093ab4f61ab2f6032d2a8db958fc7d99b5d
languageName: node
linkType: hard
"@react-hook/resize-observer@npm:^1.2.5":
version: 1.2.5
resolution: "@react-hook/resize-observer@npm:1.2.5"
dependencies:
"@juggle/resize-observer": ^3.3.1
"@react-hook/latest": ^1.0.2
"@react-hook/passive-layout-effect": ^1.2.0
"@types/raf-schd": ^4.0.0
raf-schd: ^4.0.2
peerDependencies:
react: ">=16.8"
checksum: a2f3b333e344adb3d7a4cfd2bc77fd75054b07063fba706d57bef0c497650c3e82038eafb64fa4148c08527e8b0a34f2f70392da1700a7a0d9676ee8ba795c16
languageName: node
linkType: hard
"@react-native-community/async-storage@npm:1.12.1":
version: 1.12.1
resolution: "@react-native-community/async-storage@npm:1.12.1"
@@ -4884,6 +4924,7 @@ __metadata:
"@reach/alert-dialog": 0.16.2
"@reach/menu-button": 0.16.2
"@reach/visually-hidden": 0.16.0
"@react-hook/resize-observer": ^1.2.5
"@reduxjs/toolkit": 1.8.0
"@standardnotes/editor-kit": 2.2.5
"@standardnotes/stylekit": 5.23.0
@@ -7342,6 +7383,13 @@ __metadata:
languageName: node
linkType: hard
"@types/raf-schd@npm:^4.0.0":
version: 4.0.1
resolution: "@types/raf-schd@npm:4.0.1"
checksum: 0babaa85541aadc6e5f8aa64ec79cd68bf67ea56abb7610c8daf3ca5f4b1a75d12e4e147a0b5434938a4031650ebddc733021ec4e7db4f11f7955390ec32c917
languageName: node
linkType: hard
"@types/range-parser@npm:*":
version: 1.2.4
resolution: "@types/range-parser@npm:1.2.4"