Repository navigation
Expand file tree
/
Copy pathLetterCasePicker.tsx
More file actions
85 lines (77 loc) · 3.83 KB
/
Copy pathLetterCasePicker.tsx
File metadata and controls
85 lines (77 loc) · 3.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import React, { FC, memo } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { css } from '../../styled-system/css'
import LetterCaseType from '../@types/LetterCaseType'
import { formatLetterCaseActionCreator as formatLetterCase } from '../actions/formatLetterCase'
import { isTouch } from '../browser'
import getThoughtById from '../selectors/getThoughtById'
import selectedPaths from '../selectors/selectedPaths'
import applyLetterCase from '../util/applyLetterCase'
import fastClick from '../util/fastClick'
import head from '../util/head'
import Popover from './Popover'
import LowerCaseIcon from './icons/LowerCaseIcon'
import SentenceCaseIcon from './icons/SentenceCaseIcon'
import TitleCaseIcon from './icons/TitleCaseIcon'
import UpperCaseIcon from './icons/UpperCaseIcon'
const casingTypes: LetterCaseType[] = ['LowerCase', 'UpperCase', 'SentenceCase', 'TitleCase']
/** Letter Case Picker component. */
const LetterCasePicker: FC<{ size?: number }> = memo(({ size }) => {
const dispatch = useDispatch()
const showLetterCase = useSelector(state => state.showLetterCase)
/** Toggles the Letter Case to the clicked swatch. */
const toggleLetterCase = (command: LetterCaseType, e: React.MouseEvent | React.TouchEvent) => {
e.stopPropagation()
e.preventDefault()
dispatch(formatLetterCase(command))
}
const selected = useSelector(state => {
// The swatches are only rendered while the picker is open, and deriving the letter case of a large multiselection
// is not free, so there is nothing to derive until then.
if (!state.showLetterCase) return ''
// The selected swatch is the letter case of the thoughts that formatLetterCase edits, i.e. the multiselection when
// there is one, which may have no cursor at all once the Home button has dismissed it (#4844).
const paths = selectedPaths(state)
// No swatch is selected when there is nothing to edit, otherwise `every` below would be vacuously true and
// highlight the first letter case.
if (!paths.length) return ''
const texts = paths.map(path => {
const value = getThoughtById(state, head(path))?.value || ''
// The letter case of the thought should be independent of its formatting.
return new DOMParser().parseFromString(value, 'text/html').body.textContent ?? ''
})
return casingTypes.find(type => texts.every(text => text === applyLetterCase(type, text))) ?? ''
})
return (
<Popover show={showLetterCase} size={size}>
<div aria-label='letter case swatches' className={css({ whiteSpace: 'wrap' })}>
{casingTypes.map(type => (
<div
key={type}
title={type.replace(/([a-z])([A-Z])/g, '$1 $2')}
className={css({
margin: '2px',
lineHeight: '0',
border: selected === type ? `solid 1px {colors.fg}` : `solid 1px {colors.transparent}`,
})}
aria-label={type}
data-selected={selected === type ? 'true' : 'false'}
{...fastClick(e => e.stopPropagation())}
// Apply the option on touchend rather than touchstart. React registers touchstart passively, so the handler's
// preventDefault is a no-op there and the browser goes on to synthesize mouse events from the tap, which
// land on the thought under the dropdown (#5608).
onTouchEnd={e => isTouch && toggleLetterCase(type, e)}
onMouseDown={e => !isTouch && toggleLetterCase(type, e)}
>
{type === 'LowerCase' && <LowerCaseIcon />}
{type === 'UpperCase' && <UpperCaseIcon />}
{type === 'SentenceCase' && <SentenceCaseIcon />}
{type === 'TitleCase' && <TitleCaseIcon />}
</div>
))}
</div>
</Popover>
)
})
LetterCasePicker.displayName = 'LetterCasePicker'
export default LetterCasePicker