2020-08-06 05:04:45 -04:00
|
|
|
import React, { useState, useEffect, createRef, useRef } from 'react'
|
2020-06-29 09:05:08 -04:00
|
|
|
import PropTypes from 'prop-types'
|
2020-08-24 08:20:54 -04:00
|
|
|
import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
|
2020-06-29 09:05:08 -04:00
|
|
|
import classNames from 'classnames'
|
2020-09-15 08:48:08 -04:00
|
|
|
import { useTranslation } from 'react-i18next'
|
2020-09-01 04:09:04 -04:00
|
|
|
import OutlineList from './outline-list'
|
2020-09-21 10:14:36 -04:00
|
|
|
import Icon from '../../../shared/components/icon'
|
2020-06-29 09:05:08 -04:00
|
|
|
|
2020-08-18 09:08:49 -04:00
|
|
|
function getChildrenLines(children) {
|
|
|
|
return (children || [])
|
|
|
|
.map(child => {
|
|
|
|
return getChildrenLines(child.children).concat(child.line)
|
|
|
|
})
|
|
|
|
.flat()
|
|
|
|
}
|
|
|
|
|
2020-07-28 05:37:46 -04:00
|
|
|
function OutlineItem({ outlineItem, jumpToLine, highlightedLine }) {
|
2020-09-15 08:48:08 -04:00
|
|
|
const { t } = useTranslation()
|
|
|
|
|
2020-06-29 09:05:08 -04:00
|
|
|
const [expanded, setExpanded] = useState(true)
|
2020-07-28 05:37:46 -04:00
|
|
|
const titleElementRef = createRef()
|
2020-08-06 05:04:45 -04:00
|
|
|
const isHighlightedRef = useRef(false)
|
2020-06-29 09:05:08 -04:00
|
|
|
|
|
|
|
const mainItemClasses = classNames('outline-item', {
|
|
|
|
'outline-item-no-children': !outlineItem.children
|
|
|
|
})
|
|
|
|
|
2020-08-18 09:08:49 -04:00
|
|
|
const hasHighlightedChild =
|
|
|
|
!expanded &&
|
|
|
|
getChildrenLines(outlineItem.children).includes(highlightedLine)
|
|
|
|
|
2020-09-15 08:48:08 -04:00
|
|
|
const isHighlighted =
|
|
|
|
highlightedLine === outlineItem.line || hasHighlightedChild
|
|
|
|
|
2020-07-28 05:37:46 -04:00
|
|
|
const itemLinkClasses = classNames('outline-item-link', {
|
2020-09-15 08:48:08 -04:00
|
|
|
'outline-item-link-highlight': isHighlighted
|
2020-07-28 05:37:46 -04:00
|
|
|
})
|
|
|
|
|
2020-06-29 09:05:08 -04:00
|
|
|
function handleExpandCollapseClick() {
|
|
|
|
setExpanded(!expanded)
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleOutlineItemLinkClick() {
|
2020-08-24 08:20:54 -04:00
|
|
|
jumpToLine(outlineItem.line, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleOutlineItemLinkDoubleClick() {
|
|
|
|
jumpToLine(outlineItem.line, true)
|
2020-06-29 09:05:08 -04:00
|
|
|
}
|
|
|
|
|
2020-07-28 05:37:46 -04:00
|
|
|
useEffect(
|
|
|
|
() => {
|
2020-08-06 05:04:45 -04:00
|
|
|
const wasHighlighted = isHighlightedRef.current
|
2020-09-15 08:48:27 -04:00
|
|
|
isHighlightedRef.current = isHighlighted
|
2020-07-28 05:37:46 -04:00
|
|
|
|
2020-09-15 08:48:27 -04:00
|
|
|
if (!wasHighlighted && isHighlighted) {
|
2020-08-24 08:20:54 -04:00
|
|
|
scrollIntoViewIfNeeded(titleElementRef.current, {
|
|
|
|
scrollMode: 'if-needed',
|
2020-08-06 05:04:45 -04:00
|
|
|
block: 'center'
|
|
|
|
})
|
|
|
|
}
|
2020-07-28 05:37:46 -04:00
|
|
|
},
|
2020-09-15 08:48:27 -04:00
|
|
|
[isHighlighted, titleElementRef, isHighlightedRef]
|
2020-07-28 05:37:46 -04:00
|
|
|
)
|
|
|
|
|
2020-09-15 08:48:08 -04:00
|
|
|
// don't set the aria-expanded attribute when there are no children
|
|
|
|
const ariaExpandedValue = outlineItem.children ? expanded : undefined
|
|
|
|
|
2020-06-29 09:05:08 -04:00
|
|
|
return (
|
2020-09-15 08:48:08 -04:00
|
|
|
<li
|
|
|
|
className={mainItemClasses}
|
|
|
|
aria-expanded={ariaExpandedValue}
|
|
|
|
role="treeitem"
|
|
|
|
aria-current={isHighlighted}
|
|
|
|
aria-label={outlineItem.title}
|
|
|
|
>
|
2020-06-29 09:05:08 -04:00
|
|
|
<div className="outline-item-row">
|
|
|
|
{outlineItem.children ? (
|
|
|
|
<button
|
|
|
|
className="outline-item-expand-collapse-btn"
|
|
|
|
onClick={handleExpandCollapseClick}
|
2020-09-15 08:48:08 -04:00
|
|
|
aria-label={expanded ? t('collapse') : t('expand')}
|
2020-06-29 09:05:08 -04:00
|
|
|
>
|
2020-09-21 10:14:36 -04:00
|
|
|
<Icon
|
|
|
|
type={expanded ? 'angle-down' : 'angle-right'}
|
|
|
|
classes={{ icon: 'outline-caret-icon' }}
|
|
|
|
/>
|
2020-06-29 09:05:08 -04:00
|
|
|
</button>
|
|
|
|
) : null}
|
|
|
|
<button
|
2020-07-28 05:37:46 -04:00
|
|
|
className={itemLinkClasses}
|
2020-06-29 09:05:08 -04:00
|
|
|
onClick={handleOutlineItemLinkClick}
|
2020-08-24 08:20:54 -04:00
|
|
|
onDoubleClick={handleOutlineItemLinkDoubleClick}
|
2020-07-28 05:37:46 -04:00
|
|
|
ref={titleElementRef}
|
2020-06-29 09:05:08 -04:00
|
|
|
>
|
|
|
|
{outlineItem.title}
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
{expanded && outlineItem.children ? (
|
|
|
|
<OutlineList
|
|
|
|
outline={outlineItem.children}
|
|
|
|
jumpToLine={jumpToLine}
|
|
|
|
isRoot={false}
|
2020-07-28 05:37:46 -04:00
|
|
|
highlightedLine={highlightedLine}
|
2020-06-29 09:05:08 -04:00
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
</li>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
OutlineItem.propTypes = {
|
|
|
|
outlineItem: PropTypes.exact({
|
|
|
|
line: PropTypes.number.isRequired,
|
|
|
|
title: PropTypes.string.isRequired,
|
2020-09-15 08:48:08 -04:00
|
|
|
level: PropTypes.number,
|
2020-06-29 09:05:08 -04:00
|
|
|
children: PropTypes.array
|
|
|
|
}).isRequired,
|
2020-07-28 05:37:46 -04:00
|
|
|
jumpToLine: PropTypes.func.isRequired,
|
|
|
|
highlightedLine: PropTypes.number
|
2020-06-29 09:05:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
export default OutlineItem
|