commit
7e54a30f06
25 changed files with 236 additions and 318 deletions
@ -1,60 +0,0 @@ |
||||
// @ts-check |
||||
import { FormattedMessage } from 'react-intl'; |
||||
|
||||
/** |
||||
* Returns custom renderer for one of the common counter types |
||||
* @param {"statuses" | "following" | "followers"} counterType |
||||
* Type of the counter |
||||
* @param {boolean} isBold Whether display number must be displayed in bold |
||||
* @returns {(displayNumber: JSX.Element, pluralReady: number) => JSX.Element} |
||||
* Renderer function |
||||
* @throws If counterType is not covered by this function |
||||
*/ |
||||
export function counterRenderer(counterType, isBold = true) { |
||||
/** |
||||
* @type {(displayNumber: JSX.Element) => JSX.Element} |
||||
*/ |
||||
const renderCounter = isBold |
||||
? (displayNumber) => <strong>{displayNumber}</strong> |
||||
: (displayNumber) => displayNumber; |
||||
|
||||
switch (counterType) { |
||||
case 'statuses': { |
||||
return (displayNumber, pluralReady) => ( |
||||
<FormattedMessage |
||||
id='account.statuses_counter' |
||||
defaultMessage='{count, plural, one {{counter} Post} other {{counter} Posts}}' |
||||
values={{ |
||||
count: pluralReady, |
||||
counter: renderCounter(displayNumber), |
||||
}} |
||||
/> |
||||
); |
||||
} |
||||
case 'following': { |
||||
return (displayNumber, pluralReady) => ( |
||||
<FormattedMessage |
||||
id='account.following_counter' |
||||
defaultMessage='{count, plural, one {{counter} Following} other {{counter} Following}}' |
||||
values={{ |
||||
count: pluralReady, |
||||
counter: renderCounter(displayNumber), |
||||
}} |
||||
/> |
||||
); |
||||
} |
||||
case 'followers': { |
||||
return (displayNumber, pluralReady) => ( |
||||
<FormattedMessage |
||||
id='account.followers_counter' |
||||
defaultMessage='{count, plural, one {{counter} Follower} other {{counter} Followers}}' |
||||
values={{ |
||||
count: pluralReady, |
||||
counter: renderCounter(displayNumber), |
||||
}} |
||||
/> |
||||
); |
||||
} |
||||
default: throw Error(`Incorrect counter name: ${counterType}. Ensure it accepted by commonCounter function`); |
||||
} |
||||
} |
@ -0,0 +1,45 @@ |
||||
import React from 'react'; |
||||
|
||||
import { FormattedMessage } from 'react-intl'; |
||||
|
||||
export const StatusesCounter = ( |
||||
displayNumber: React.ReactNode, |
||||
pluralReady: number |
||||
) => ( |
||||
<FormattedMessage |
||||
id='account.statuses_counter' |
||||
defaultMessage='{count, plural, one {{counter} Post} other {{counter} Posts}}' |
||||
values={{ |
||||
count: pluralReady, |
||||
counter: <strong>{displayNumber}</strong>, |
||||
}} |
||||
/> |
||||
); |
||||
|
||||
export const FollowingCounter = ( |
||||
displayNumber: React.ReactNode, |
||||
pluralReady: number |
||||
) => ( |
||||
<FormattedMessage |
||||
id='account.following_counter' |
||||
defaultMessage='{count, plural, one {{counter} Following} other {{counter} Following}}' |
||||
values={{ |
||||
count: pluralReady, |
||||
counter: <strong>{displayNumber}</strong>, |
||||
}} |
||||
/> |
||||
); |
||||
|
||||
export const FollowersCounter = ( |
||||
displayNumber: React.ReactNode, |
||||
pluralReady: number |
||||
) => ( |
||||
<FormattedMessage |
||||
id='account.followers_counter' |
||||
defaultMessage='{count, plural, one {{counter} Follower} other {{counter} Followers}}' |
||||
values={{ |
||||
count: pluralReady, |
||||
counter: <strong>{displayNumber}</strong>, |
||||
}} |
||||
/> |
||||
); |
@ -1,55 +0,0 @@ |
||||
import PropTypes from 'prop-types'; |
||||
import { PureComponent } from 'react'; |
||||
|
||||
import { injectIntl, defineMessages } from 'react-intl'; |
||||
|
||||
import { bannerSettings } from 'mastodon/settings'; |
||||
|
||||
import { IconButton } from './icon_button'; |
||||
|
||||
const messages = defineMessages({ |
||||
dismiss: { id: 'dismissable_banner.dismiss', defaultMessage: 'Dismiss' }, |
||||
}); |
||||
|
||||
class DismissableBanner extends PureComponent { |
||||
|
||||
static propTypes = { |
||||
id: PropTypes.string.isRequired, |
||||
children: PropTypes.node, |
||||
intl: PropTypes.object.isRequired, |
||||
}; |
||||
|
||||
state = { |
||||
visible: !bannerSettings.get(this.props.id), |
||||
}; |
||||
|
||||
handleDismiss = () => { |
||||
const { id } = this.props; |
||||
this.setState({ visible: false }, () => bannerSettings.set(id, true)); |
||||
}; |
||||
|
||||
render () { |
||||
const { visible } = this.state; |
||||
|
||||
if (!visible) { |
||||
return null; |
||||
} |
||||
|
||||
const { children, intl } = this.props; |
||||
|
||||
return ( |
||||
<div className='dismissable-banner'> |
||||
<div className='dismissable-banner__message'> |
||||
{children} |
||||
</div> |
||||
|
||||
<div className='dismissable-banner__action'> |
||||
<IconButton icon='times' title={intl.formatMessage(messages.dismiss)} onClick={this.handleDismiss} /> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
} |
||||
|
||||
export default injectIntl(DismissableBanner); |
@ -0,0 +1,47 @@ |
||||
import type { PropsWithChildren } from 'react'; |
||||
import { useCallback, useState } from 'react'; |
||||
|
||||
import { defineMessages, useIntl } from 'react-intl'; |
||||
|
||||
import { bannerSettings } from 'mastodon/settings'; |
||||
|
||||
import { IconButton } from './icon_button'; |
||||
|
||||
const messages = defineMessages({ |
||||
dismiss: { id: 'dismissable_banner.dismiss', defaultMessage: 'Dismiss' }, |
||||
}); |
||||
|
||||
interface Props { |
||||
id: string; |
||||
} |
||||
|
||||
export const DismissableBanner: React.FC<PropsWithChildren<Props>> = ({ |
||||
id, |
||||
children, |
||||
}) => { |
||||
const [visible, setVisible] = useState(!bannerSettings.get(id)); |
||||
const intl = useIntl(); |
||||
|
||||
const handleDismiss = useCallback(() => { |
||||
setVisible(false); |
||||
bannerSettings.set(id, true); |
||||
}, [id]); |
||||
|
||||
if (!visible) { |
||||
return null; |
||||
} |
||||
|
||||
return ( |
||||
<div className='dismissable-banner'> |
||||
<div className='dismissable-banner__message'>{children}</div> |
||||
|
||||
<div className='dismissable-banner__action'> |
||||
<IconButton |
||||
icon='times' |
||||
title={intl.formatMessage(messages.dismiss)} |
||||
onClick={handleDismiss} |
||||
/> |
||||
</div> |
||||
</div> |
||||
); |
||||
}; |
@ -1,115 +0,0 @@ |
||||
import PropTypes from 'prop-types'; |
||||
import { memo } from 'react'; |
||||
|
||||
import { FormattedMessage, FormattedNumber } from 'react-intl'; |
||||
|
||||
import { toShortNumber, pluralReady, DECIMAL_UNITS } from '../utils/numbers'; |
||||
|
||||
// @ts-check |
||||
|
||||
/** |
||||
* @callback ShortNumberRenderer |
||||
* @param {JSX.Element} displayNumber Number to display |
||||
* @param {number} pluralReady Number used for pluralization |
||||
* @returns {JSX.Element} Final render of number |
||||
*/ |
||||
|
||||
/** |
||||
* @typedef {object} ShortNumberProps |
||||
* @property {number} value Number to display in short variant |
||||
* @property {ShortNumberRenderer} [renderer] |
||||
* Custom renderer for numbers, provided as a prop. If another renderer |
||||
* passed as a child of this component, this prop won't be used. |
||||
* @property {ShortNumberRenderer} [children] |
||||
* Custom renderer for numbers, provided as a child. If another renderer |
||||
* passed as a prop of this component, this one will be used instead. |
||||
*/ |
||||
|
||||
/** |
||||
* Component that renders short big number to a shorter version |
||||
* @param {ShortNumberProps} param0 Props for the component |
||||
* @returns {JSX.Element} Rendered number |
||||
*/ |
||||
function ShortNumber({ value, renderer, children }) { |
||||
const shortNumber = toShortNumber(value); |
||||
const [, division] = shortNumber; |
||||
|
||||
if (children != null && renderer != null) { |
||||
console.warn('Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.'); |
||||
} |
||||
|
||||
const customRenderer = children != null ? children : renderer; |
||||
|
||||
const displayNumber = <ShortNumberCounter value={shortNumber} />; |
||||
|
||||
return customRenderer != null |
||||
? customRenderer(displayNumber, pluralReady(value, division)) |
||||
: displayNumber; |
||||
} |
||||
|
||||
ShortNumber.propTypes = { |
||||
value: PropTypes.number.isRequired, |
||||
renderer: PropTypes.func, |
||||
children: PropTypes.func, |
||||
}; |
||||
|
||||
/** |
||||
* @typedef {object} ShortNumberCounterProps |
||||
* @property {import('../utils/number').ShortNumber} value Short number |
||||
*/ |
||||
|
||||
/** |
||||
* Renders short number into corresponding localizable react fragment |
||||
* @param {ShortNumberCounterProps} param0 Props for the component |
||||
* @returns {JSX.Element} FormattedMessage ready to be embedded in code |
||||
*/ |
||||
function ShortNumberCounter({ value }) { |
||||
const [rawNumber, unit, maxFractionDigits = 0] = value; |
||||
|
||||
const count = ( |
||||
<FormattedNumber |
||||
value={rawNumber} |
||||
maximumFractionDigits={maxFractionDigits} |
||||
/> |
||||
); |
||||
|
||||
let values = { count, rawNumber }; |
||||
|
||||
switch (unit) { |
||||
case DECIMAL_UNITS.THOUSAND: { |
||||
return ( |
||||
<FormattedMessage |
||||
id='units.short.thousand' |
||||
defaultMessage='{count}K' |
||||
values={values} |
||||
/> |
||||
); |
||||
} |
||||
case DECIMAL_UNITS.MILLION: { |
||||
return ( |
||||
<FormattedMessage |
||||
id='units.short.million' |
||||
defaultMessage='{count}M' |
||||
values={values} |
||||
/> |
||||
); |
||||
} |
||||
case DECIMAL_UNITS.BILLION: { |
||||
return ( |
||||
<FormattedMessage |
||||
id='units.short.billion' |
||||
defaultMessage='{count}B' |
||||
values={values} |
||||
/> |
||||
); |
||||
} |
||||
// Not sure if we should go farther - @Sasha-Sorokin |
||||
default: return count; |
||||
} |
||||
} |
||||
|
||||
ShortNumberCounter.propTypes = { |
||||
value: PropTypes.arrayOf(PropTypes.number), |
||||
}; |
||||
|
||||
export default memo(ShortNumber); |
@ -0,0 +1,90 @@ |
||||
import { memo } from 'react'; |
||||
|
||||
import { FormattedMessage, FormattedNumber } from 'react-intl'; |
||||
|
||||
import { toShortNumber, pluralReady, DECIMAL_UNITS } from '../utils/numbers'; |
||||
|
||||
type ShortNumberRenderer = ( |
||||
displayNumber: JSX.Element, |
||||
pluralReady: number |
||||
) => JSX.Element; |
||||
|
||||
interface ShortNumberProps { |
||||
value: number; |
||||
renderer?: ShortNumberRenderer; |
||||
children?: ShortNumberRenderer; |
||||
} |
||||
|
||||
export const ShortNumberRenderer: React.FC<ShortNumberProps> = ({ |
||||
value, |
||||
renderer, |
||||
children, |
||||
}) => { |
||||
const shortNumber = toShortNumber(value); |
||||
const [, division] = shortNumber; |
||||
|
||||
if (children && renderer) { |
||||
console.warn( |
||||
'Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.' |
||||
); |
||||
} |
||||
|
||||
const customRenderer = children || renderer || null; |
||||
|
||||
const displayNumber = <ShortNumberCounter value={shortNumber} />; |
||||
|
||||
return ( |
||||
customRenderer?.(displayNumber, pluralReady(value, division)) || |
||||
displayNumber |
||||
); |
||||
}; |
||||
export const ShortNumber = memo(ShortNumberRenderer); |
||||
|
||||
interface ShortNumberCounterProps { |
||||
value: number[]; |
||||
} |
||||
const ShortNumberCounter: React.FC<ShortNumberCounterProps> = ({ value }) => { |
||||
const [rawNumber, unit, maxFractionDigits = 0] = value; |
||||
|
||||
const count = ( |
||||
<FormattedNumber |
||||
value={rawNumber} |
||||
maximumFractionDigits={maxFractionDigits} |
||||
/> |
||||
); |
||||
|
||||
const values = { count, rawNumber }; |
||||
|
||||
switch (unit) { |
||||
case DECIMAL_UNITS.THOUSAND: { |
||||
return ( |
||||
<FormattedMessage |
||||
id='units.short.thousand' |
||||
defaultMessage='{count}K' |
||||
values={values} |
||||
/> |
||||
); |
||||
} |
||||
case DECIMAL_UNITS.MILLION: { |
||||
return ( |
||||
<FormattedMessage |
||||
id='units.short.million' |
||||
defaultMessage='{count}M' |
||||
values={values} |
||||
/> |
||||
); |
||||
} |
||||
case DECIMAL_UNITS.BILLION: { |
||||
return ( |
||||
<FormattedMessage |
||||
id='units.short.billion' |
||||
defaultMessage='{count}B' |
||||
values={values} |
||||
/> |
||||
); |
||||
} |
||||
// Not sure if we should go farther - @Sasha-Sorokin
|
||||
default: |
||||
return count; |
||||
} |
||||
}; |
Loading…
Reference in new issue