refactor: replace 'preact' with 'react' (#1048)

This commit is contained in:
Aman Harwara
2022-05-30 12:42:52 +05:30
committed by GitHub
parent e74b4953ea
commit 8c368dd96b
231 changed files with 4794 additions and 4302 deletions

View File

@@ -1,21 +1,26 @@
import { JSX, FunctionComponent, ComponentChildren, VNode, RefCallback, ComponentChild, toChildArray } from 'preact'
import { useCallback, useEffect, useRef } from 'preact/hooks'
import { JSXInternal } from 'preact/src/jsx'
import { MenuItem, MenuItemListElement } from './MenuItem'
import {
CSSProperties,
FunctionComponent,
KeyboardEventHandler,
ReactNode,
useCallback,
useEffect,
useRef,
} from 'react'
import { KeyboardKey } from '@/Services/IOService'
import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation'
type MenuProps = {
className?: string
style?: string | JSX.CSSProperties | undefined
style?: CSSProperties | undefined
a11yLabel: string
children: ComponentChildren
children: ReactNode
closeMenu?: () => void
isOpen: boolean
initialFocus?: number
}
export const Menu: FunctionComponent<MenuProps> = ({
const Menu: FunctionComponent<MenuProps> = ({
children,
className = '',
style,
@@ -24,16 +29,10 @@ export const Menu: FunctionComponent<MenuProps> = ({
isOpen,
initialFocus,
}: MenuProps) => {
const menuItemRefs = useRef<(HTMLButtonElement | null)[]>([])
const menuElementRef = useRef<HTMLMenuElement>(null)
const handleKeyDown: JSXInternal.KeyboardEventHandler<HTMLMenuElement> = useCallback(
const handleKeyDown: KeyboardEventHandler<HTMLMenuElement> = useCallback(
(event) => {
if (!menuItemRefs.current) {
return
}
if (event.key === KeyboardKey.Escape) {
closeMenu?.()
return
@@ -45,58 +44,13 @@ export const Menu: FunctionComponent<MenuProps> = ({
useListKeyboardNavigation(menuElementRef, initialFocus)
useEffect(() => {
if (isOpen && menuItemRefs.current.length > 0) {
if (isOpen) {
setTimeout(() => {
menuElementRef.current?.focus()
})
}
}, [isOpen])
const pushRefToArray: RefCallback<HTMLLIElement> = useCallback((instance) => {
if (instance && instance.children) {
Array.from(instance.children).forEach((child) => {
if (
child.getAttribute('role')?.includes('menuitem') &&
!menuItemRefs.current.includes(child as HTMLButtonElement)
) {
menuItemRefs.current.push(child as HTMLButtonElement)
}
})
}
}, [])
const mapMenuItems = useCallback(
(child: ComponentChild, index: number, array: ComponentChild[]): ComponentChild => {
if (!child || (Array.isArray(child) && child.length < 1)) {
return
}
if (Array.isArray(child)) {
return child.map(mapMenuItems)
}
const _child = child as VNode<unknown>
const isFirstMenuItem = index === array.findIndex((child) => (child as VNode<unknown>).type === MenuItem)
const hasMultipleItems = Array.isArray(_child.props.children)
? Array.from(_child.props.children as ComponentChild[]).some(
(child) => (child as VNode<unknown>).type === MenuItem,
)
: false
const items = hasMultipleItems ? [...(_child.props.children as ComponentChild[])] : [_child]
return items.map((child) => {
return (
<MenuItemListElement isFirstMenuItem={isFirstMenuItem} ref={pushRefToArray}>
{child}
</MenuItemListElement>
)
})
},
[pushRefToArray],
)
return (
<menu
className={`m-0 p-0 list-style-none focus:shadow-none ${className}`}
@@ -105,7 +59,9 @@ export const Menu: FunctionComponent<MenuProps> = ({
style={style}
aria-label={a11yLabel}
>
{toChildArray(children).map(mapMenuItems)}
{children}
</menu>
)
}
export default Menu

View File

@@ -1,21 +1,15 @@
import { ComponentChildren, FunctionComponent, VNode } from 'preact'
import { forwardRef, Ref } from 'preact/compat'
import { JSXInternal } from 'preact/src/jsx'
import { Icon } from '@/Components/Icon/Icon'
import { Switch, SwitchProps } from '@/Components/Switch/Switch'
import { forwardRef, MouseEventHandler, ReactNode, Ref } from 'react'
import Icon from '@/Components/Icon/Icon'
import Switch from '@/Components/Switch/Switch'
import { SwitchProps } from '@/Components/Switch/SwitchProps'
import { IconType } from '@standardnotes/snjs'
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants'
export enum MenuItemType {
IconButton,
RadioButton,
SwitchButton,
}
import { MenuItemType } from './MenuItemType'
type MenuItemProps = {
type: MenuItemType
children: ComponentChildren
onClick?: JSXInternal.MouseEventHandler<HTMLButtonElement>
children: ReactNode
onClick?: MouseEventHandler<HTMLButtonElement>
onChange?: SwitchProps['onChange']
onBlur?: (event: { relatedTarget: EventTarget | null }) => void
className?: string
@@ -25,7 +19,7 @@ type MenuItemProps = {
tabIndex?: number
}
export const MenuItem: FunctionComponent<MenuItemProps> = forwardRef(
const MenuItem = forwardRef(
(
{
children,
@@ -42,63 +36,42 @@ export const MenuItem: FunctionComponent<MenuItemProps> = forwardRef(
ref: Ref<HTMLButtonElement>,
) => {
return type === MenuItemType.SwitchButton && typeof onChange === 'function' ? (
<button
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none justify-between"
onClick={() => {
onChange(!checked)
}}
onBlur={onBlur}
tabIndex={typeof tabIndex === 'number' ? tabIndex : FOCUSABLE_BUT_NOT_TABBABLE}
role="menuitemcheckbox"
aria-checked={checked}
>
<span className="flex flex-grow items-center">{children}</span>
<Switch className="px-0" checked={checked} />
</button>
<li className="list-style-none" role="none">
<button
ref={ref}
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none justify-between"
onClick={() => {
onChange(!checked)
}}
onBlur={onBlur}
tabIndex={typeof tabIndex === 'number' ? tabIndex : FOCUSABLE_BUT_NOT_TABBABLE}
role="menuitemcheckbox"
aria-checked={checked}
>
<span className="flex flex-grow items-center">{children}</span>
<Switch className="px-0" checked={checked} />
</button>
</li>
) : (
<button
ref={ref}
role={type === MenuItemType.RadioButton ? 'menuitemradio' : 'menuitem'}
tabIndex={typeof tabIndex === 'number' ? tabIndex : FOCUSABLE_BUT_NOT_TABBABLE}
className={`sn-dropdown-item focus:bg-info-backdrop focus:shadow-none ${className}`}
onClick={onClick}
onBlur={onBlur}
{...(type === MenuItemType.RadioButton ? { 'aria-checked': checked } : {})}
>
{type === MenuItemType.IconButton && icon ? <Icon type={icon} className={iconClassName} /> : null}
{type === MenuItemType.RadioButton && typeof checked === 'boolean' ? (
<div className={`pseudo-radio-btn ${checked ? 'pseudo-radio-btn--checked' : ''} flex-shrink-0`}></div>
) : null}
{children}
</button>
)
},
)
export const MenuItemSeparator: FunctionComponent = () => <div role="separator" className="h-1px my-2 bg-border"></div>
type ListElementProps = {
isFirstMenuItem: boolean
children: ComponentChildren
}
export const MenuItemListElement: FunctionComponent<ListElementProps> = forwardRef(
({ children, isFirstMenuItem }: ListElementProps, ref: Ref<HTMLLIElement>) => {
const child = children as VNode<unknown>
return (
<li className="list-style-none" role="none" ref={ref}>
{{
...child,
props: {
...(child.props ? { ...child.props } : {}),
...(child.type === MenuItem
? {
tabIndex: isFirstMenuItem ? 0 : -1,
}
: {}),
},
}}
<li className="list-style-none" role="none">
<button
ref={ref}
role={type === MenuItemType.RadioButton ? 'menuitemradio' : 'menuitem'}
tabIndex={typeof tabIndex === 'number' ? tabIndex : FOCUSABLE_BUT_NOT_TABBABLE}
className={`sn-dropdown-item focus:bg-info-backdrop focus:shadow-none ${className}`}
onClick={onClick}
onBlur={onBlur}
{...(type === MenuItemType.RadioButton ? { 'aria-checked': checked } : {})}
>
{type === MenuItemType.IconButton && icon ? <Icon type={icon} className={iconClassName} /> : null}
{type === MenuItemType.RadioButton && typeof checked === 'boolean' ? (
<div className={`pseudo-radio-btn ${checked ? 'pseudo-radio-btn--checked' : ''} flex-shrink-0`}></div>
) : null}
{children}
</button>
</li>
)
},
)
export default MenuItem

View File

@@ -0,0 +1,9 @@
import { FunctionComponent } from 'react'
const MenuItemSeparator: FunctionComponent = () => (
<li className="list-style-none" role="none">
<div role="separator" className="h-1px my-2 bg-border" />
</li>
)
export default MenuItemSeparator

View File

@@ -0,0 +1,5 @@
export enum MenuItemType {
IconButton,
RadioButton,
SwitchButton,
}