Visual Character counter for post titles, poll options (#466)

* Indicate how many chars remain for title field and poll options

Live counter update to help authors know how many more chars they have
to use in their post titles, and also poll options

* Use InputInner for consistency

* Refactor to reuse title hint across all forms

* Character(s)

* Move maxLength hint impl to InputInner, per PR feedback

---------

Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
SatsAllDay 2023-09-11 20:20:44 -04:00 committed by GitHub
parent cd3dbeb19b
commit 77daa458cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 24 additions and 2 deletions

View File

@ -10,6 +10,7 @@ import { SubSelectInitial } from './sub-select-form'
import CancelButton from './cancel-button'
import { useCallback } from 'react'
import { normalizeForwards } from '../lib/form'
import { MAX_TITLE_LENGTH } from '../lib/constants'
export function BountyForm ({
item,
@ -98,7 +99,14 @@ export function BountyForm ({
storageKeyPrefix={item ? undefined : 'bounty'}
>
{children}
<Input label={titleLabel} name='title' required autoFocus clear />
<Input
label={titleLabel}
name='title'
required
autoFocus
clear
maxLength={MAX_TITLE_LENGTH}
/>
<Input
label={bountyLabel} name='bounty' required
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}

View File

@ -14,6 +14,7 @@ import { SubSelectInitial } from './sub-select-form'
import CancelButton from './cancel-button'
import { useCallback } from 'react'
import { normalizeForwards } from '../lib/form'
import { MAX_TITLE_LENGTH } from '../lib/constants'
export function DiscussionForm ({
item, sub, editThreshold, titleLabel = 'title',
@ -101,6 +102,7 @@ export function DiscussionForm ({
})
}
}}
maxLength={MAX_TITLE_LENGTH}
/>
<MarkdownInput
topLevel

View File

@ -20,6 +20,7 @@ import { USER_SEARCH } from '../fragments/users'
import TextareaAutosize from 'react-textarea-autosize'
import { useToast } from './toast'
import { useInvoiceable } from './invoice'
import { numWithUnits } from '../lib/format'
export function SubmitButton ({
children, variant, value, onClick, disabled, cost, ...props
@ -320,6 +321,11 @@ function InputInner ({
{hint}
</BootstrapForm.Text>
)}
{props.maxLength && (
<BootstrapForm.Text>
{`${numWithUnits(props.maxLength - (field.value || '').length, { abbreviate: false, unitSingular: 'character', unitPlural: 'characters' })} remaining`}
</BootstrapForm.Text>
)}
</>
)
}

View File

@ -17,6 +17,7 @@ import Avatar from './avatar'
import ActionTooltip from './action-tooltip'
import { jobSchema } from '../lib/validate'
import CancelButton from './cancel-button'
import { MAX_TITLE_LENGTH } from '../lib/constants'
function satsMin2Mo (minute) {
return minute * 30 * 24 * 60
@ -116,6 +117,7 @@ export default function JobForm ({ item, sub }) {
required
autoFocus
clear
maxLength={MAX_TITLE_LENGTH}
/>
<Input
label='company'

View File

@ -15,6 +15,7 @@ import Moon from '../svgs/moon-fill.svg'
import { SubSelectInitial } from './sub-select-form'
import CancelButton from './cancel-button'
import { normalizeForwards } from '../lib/form'
import { MAX_TITLE_LENGTH } from '../lib/constants'
export function LinkForm ({ item, sub, editThreshold, children }) {
const router = useRouter()
@ -146,6 +147,7 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()))
}
}}
maxLength={MAX_TITLE_LENGTH}
/>
<Input
label='url'

View File

@ -3,7 +3,7 @@ import { useRouter } from 'next/router'
import { gql, useApolloClient, useMutation } from '@apollo/client'
import Countdown from './countdown'
import AdvPostForm, { AdvPostInitial } from './adv-post-form'
import { MAX_POLL_NUM_CHOICES } from '../lib/constants'
import { MAX_POLL_CHOICE_LENGTH, MAX_POLL_NUM_CHOICES, MAX_TITLE_LENGTH } from '../lib/constants'
import FeeButton, { EditFeeButton } from './fee-button'
import Delete from './delete'
import Button from 'react-bootstrap/Button'
@ -76,6 +76,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
label='title'
name='title'
required
maxLength={MAX_TITLE_LENGTH}
/>
<MarkdownInput
topLevel
@ -92,6 +93,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
hint={editThreshold
? <div className='text-muted fw-bold'><Countdown date={editThreshold} /></div>
: null}
maxLength={MAX_POLL_CHOICE_LENGTH}
/>
<AdvPostForm edit={!!item} />
<div className='mt-3'>