[Proposal] Make able to write React in Typescript (#16210)
Co-authored-by: berlysia <berlysia@gmail.com> Co-authored-by: fusagiko / takayamaki <takayamaki@users.noreply.github.com>local
parent
2f7c3cb628
commit
4520e6473a
26 changed files with 1100 additions and 212 deletions
@ -0,0 +1,17 @@ |
|||||||
|
import { useCallback, useState } from 'react'; |
||||||
|
|
||||||
|
export const useHovering = (animate?: boolean) => { |
||||||
|
const [hovering, setHovering] = useState<boolean>(animate ?? false); |
||||||
|
|
||||||
|
const handleMouseEnter = useCallback(() => { |
||||||
|
if (animate) return; |
||||||
|
setHovering(true); |
||||||
|
}, [animate]); |
||||||
|
|
||||||
|
const handleMouseLeave = useCallback(() => { |
||||||
|
if (animate) return; |
||||||
|
setHovering(false); |
||||||
|
}, [animate]); |
||||||
|
|
||||||
|
return { hovering, handleMouseEnter, handleMouseLeave }; |
||||||
|
}; |
@ -1,62 +0,0 @@ |
|||||||
import React from 'react'; |
|
||||||
import PropTypes from 'prop-types'; |
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
|
||||||
import { autoPlayGif } from '../initial_state'; |
|
||||||
import classNames from 'classnames'; |
|
||||||
|
|
||||||
export default class Avatar extends React.PureComponent { |
|
||||||
|
|
||||||
static propTypes = { |
|
||||||
account: ImmutablePropTypes.map, |
|
||||||
size: PropTypes.number.isRequired, |
|
||||||
style: PropTypes.object, |
|
||||||
inline: PropTypes.bool, |
|
||||||
animate: PropTypes.bool, |
|
||||||
}; |
|
||||||
|
|
||||||
static defaultProps = { |
|
||||||
animate: autoPlayGif, |
|
||||||
size: 20, |
|
||||||
inline: false, |
|
||||||
}; |
|
||||||
|
|
||||||
state = { |
|
||||||
hovering: false, |
|
||||||
}; |
|
||||||
|
|
||||||
handleMouseEnter = () => { |
|
||||||
if (this.props.animate) return; |
|
||||||
this.setState({ hovering: true }); |
|
||||||
}; |
|
||||||
|
|
||||||
handleMouseLeave = () => { |
|
||||||
if (this.props.animate) return; |
|
||||||
this.setState({ hovering: false }); |
|
||||||
}; |
|
||||||
|
|
||||||
render () { |
|
||||||
const { account, size, animate, inline } = this.props; |
|
||||||
const { hovering } = this.state; |
|
||||||
|
|
||||||
const style = { |
|
||||||
...this.props.style, |
|
||||||
width: `${size}px`, |
|
||||||
height: `${size}px`, |
|
||||||
}; |
|
||||||
|
|
||||||
let src; |
|
||||||
|
|
||||||
if (hovering || animate) { |
|
||||||
src = account?.get('avatar'); |
|
||||||
} else { |
|
||||||
src = account?.get('avatar_static'); |
|
||||||
} |
|
||||||
|
|
||||||
return ( |
|
||||||
<div className={classNames('account__avatar', { 'account__avatar-inline': inline })} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} style={style}> |
|
||||||
{src && <img src={src} alt={account?.get('acct')} />} |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,40 @@ |
|||||||
|
import * as React from 'react'; |
||||||
|
import classNames from 'classnames'; |
||||||
|
import { autoPlayGif } from '../initial_state'; |
||||||
|
import { useHovering } from '../../hooks/useHovering'; |
||||||
|
import type { Account } from '../../types/resources'; |
||||||
|
|
||||||
|
type Props = { |
||||||
|
account: Account; |
||||||
|
size: number; |
||||||
|
style?: React.CSSProperties; |
||||||
|
inline?: boolean; |
||||||
|
animate?: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
export const Avatar: React.FC<Props> = ({ |
||||||
|
account, |
||||||
|
animate = autoPlayGif, |
||||||
|
size = 20, |
||||||
|
inline = false, |
||||||
|
style: styleFromParent, |
||||||
|
}) => { |
||||||
|
|
||||||
|
const { hovering, handleMouseEnter, handleMouseLeave } = useHovering(animate); |
||||||
|
|
||||||
|
const style = { |
||||||
|
...styleFromParent, |
||||||
|
width: `${size}px`, |
||||||
|
height: `${size}px`, |
||||||
|
}; |
||||||
|
|
||||||
|
const src = (hovering || animate) ? account?.get('avatar') : account?.get('avatar_static'); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className={classNames('account__avatar', { 'account__avatar-inline': inline })} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} style={style}> |
||||||
|
{src && <img src={src} alt={account?.get('acct')} />} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default Avatar; |
@ -1,3 +0,0 @@ |
|||||||
export default function uuid(a) { |
|
||||||
return a ? (a^Math.random() * 16 >> a / 4).toString(16) : ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, uuid); |
|
||||||
} |
|
@ -0,0 +1,3 @@ |
|||||||
|
export default function uuid(a?: string): string { |
||||||
|
return a ? ((a as any as number) ^ Math.random() * 16 >> (a as any as number) / 4).toString(16) : ('' + 1e7 + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, uuid); |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
interface MastodonMap<T> { |
||||||
|
get<K extends keyof T>(key: K): T[K]; |
||||||
|
has<K extends keyof T>(key: K): boolean; |
||||||
|
set<K extends keyof T>(key: K, value: T[K]): this; |
||||||
|
} |
||||||
|
|
||||||
|
type AccountValues = { |
||||||
|
id: number; |
||||||
|
avatar: string; |
||||||
|
avatar_static: string; |
||||||
|
[key: string]: any; |
||||||
|
} |
||||||
|
export type Account = MastodonMap<AccountValues> |
@ -0,0 +1,13 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"jsx": "react", |
||||||
|
"target": "esnext", |
||||||
|
"moduleResolution": "node", |
||||||
|
"allowJs": true, |
||||||
|
"noEmit": true, |
||||||
|
"strict": true, |
||||||
|
"esModuleInterop": true, |
||||||
|
"skipLibCheck": true |
||||||
|
}, |
||||||
|
"include": ["app/javascript/mastodon", "app/javascript/packs"] |
||||||
|
} |
Loading…
Reference in new issue