More comment create and textarea improvements
This commit is contained in:
parent
b3ed0027ff
commit
eef6691557
16 changed files with 250 additions and 135 deletions
|
@ -484,8 +484,8 @@ export function CommentCreate(props: Props) {
|
|||
disabled={isFetchingChannels || disableInput}
|
||||
isLivestream={isLivestream}
|
||||
label={
|
||||
<div className="commentCreate__labelWrapper">
|
||||
<span className="commentCreate__label">
|
||||
<div className="comment-create__label-wrapper">
|
||||
<span className="comment-create__label">
|
||||
{(isReply ? __('Replying as') : isLivestream ? __('Chat as') : __('Comment as')) + ' '}
|
||||
</span>
|
||||
<SelectChannel tiny />
|
||||
|
|
|
@ -253,7 +253,7 @@ function CommentMenuList(props: Props) {
|
|||
</MenuItem>
|
||||
)}
|
||||
|
||||
{isPinned && (
|
||||
{isPinned && isLiveComment && (
|
||||
<MenuItem className="comment__menu-option menu__link" onSelect={handleDismissPin}>
|
||||
<Icon aria-hidden icon={ICONS.DISMISS_ALL} />
|
||||
{__('Dismiss Pin')}
|
||||
|
|
|
@ -272,8 +272,8 @@ export class FormField extends React.PureComponent<Props> {
|
|||
</React.Suspense>
|
||||
)}
|
||||
|
||||
<div className="form-field__textarea-info">
|
||||
{!noEmojis && openEmoteMenu && (
|
||||
{!noEmojis && openEmoteMenu && (
|
||||
<div className="form-field__textarea-info">
|
||||
<Button
|
||||
type="alt"
|
||||
className="button--file-action"
|
||||
|
@ -282,8 +282,8 @@ export class FormField extends React.PureComponent<Props> {
|
|||
icon={ICONS.EMOJI}
|
||||
iconSize={20}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</fieldset-section>
|
||||
);
|
||||
default:
|
||||
|
|
|
@ -297,7 +297,6 @@ export default function LivestreamChatLayout(props: Props) {
|
|||
key={pinnedComment.comment_id}
|
||||
uri={uri}
|
||||
pushMention={setMention}
|
||||
handleDismissPin={() => setShowPinned(false)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import LivestreamLayout from './view';
|
||||
import SwipeableDrawer from './view';
|
||||
import { selectTheme } from 'redux/selectors/settings';
|
||||
import { selectMobilePlayerDimensions } from 'redux/selectors/app';
|
||||
|
||||
|
@ -8,4 +8,4 @@ const select = (state) => ({
|
|||
mobilePlayerDimensions: selectMobilePlayerDimensions(state),
|
||||
});
|
||||
|
||||
export default connect(select)(LivestreamLayout);
|
||||
export default connect(select)(SwipeableDrawer);
|
||||
|
|
|
@ -42,29 +42,18 @@ export default function SwipeableDrawer(props: Props) {
|
|||
}
|
||||
}, [coverHeight, mobilePlayerDimensions, open]);
|
||||
|
||||
const DrawerGlobalStyles = () => (
|
||||
<Global
|
||||
styles={{
|
||||
'.main-wrapper__inner--filepage': {
|
||||
overflow: open ? 'hidden' : 'unset',
|
||||
maxHeight: open ? '100vh' : 'unset',
|
||||
},
|
||||
'.MuiDrawer-root': {
|
||||
top: `calc(${HEADER_HEIGHT_MOBILE}px + ${videoHeight}px) !important`,
|
||||
},
|
||||
'.MuiDrawer-root > .MuiPaper-root': {
|
||||
overflow: 'visible',
|
||||
color: 'var(--color-text)',
|
||||
position: 'absolute',
|
||||
height: `calc(100% - ${DRAWER_PULLER_HEIGHT}px)`,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
// Reset scroll position when opening: avoid broken position where
|
||||
// the drawer is lower than the video
|
||||
React.useEffect(() => {
|
||||
if (open) {
|
||||
const htmlEl = document.querySelector('html');
|
||||
if (htmlEl) htmlEl.scrollTop = 0;
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DrawerGlobalStyles />
|
||||
<DrawerGlobalStyles open={open} videoHeight={videoHeight} />
|
||||
|
||||
<MUIDrawer
|
||||
anchor="bottom"
|
||||
|
@ -90,6 +79,35 @@ export default function SwipeableDrawer(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
type GlobalStylesProps = {
|
||||
open?: boolean,
|
||||
videoHeight: number,
|
||||
};
|
||||
|
||||
const DrawerGlobalStyles = (globalStylesProps: GlobalStylesProps) => {
|
||||
const { open, videoHeight } = globalStylesProps;
|
||||
|
||||
return (
|
||||
<Global
|
||||
styles={{
|
||||
'.main-wrapper__inner--filepage': {
|
||||
overflow: open ? 'hidden' : 'unset',
|
||||
maxHeight: open ? '100vh' : 'unset',
|
||||
},
|
||||
'.main-wrapper .MuiDrawer-root': {
|
||||
top: `calc(${HEADER_HEIGHT_MOBILE}px + ${videoHeight}px) !important`,
|
||||
},
|
||||
'.main-wrapper .MuiDrawer-root > .MuiPaper-root': {
|
||||
overflow: 'visible',
|
||||
color: 'var(--color-text)',
|
||||
position: 'absolute',
|
||||
height: `calc(100% - ${DRAWER_PULLER_HEIGHT}px)`,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type PullerProps = {
|
||||
theme: string,
|
||||
};
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectClaimForUri } from 'redux/selectors/claims';
|
||||
import TextareaSuggestionsItem from './view';
|
||||
import { formatLbryChannelName } from 'util/url';
|
||||
import { getClaimTitle } from 'util/claim';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: props.uri && selectClaimForUri(state, props.uri),
|
||||
});
|
||||
const select = (state, props) => {
|
||||
const { uri } = props;
|
||||
|
||||
const claim = uri && selectClaimForUri(state, uri);
|
||||
|
||||
return {
|
||||
claimLabel: claim && formatLbryChannelName(claim.canonical_url),
|
||||
claimTitle: claim && getClaimTitle(claim.canonical_url),
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(select)(TextareaSuggestionsItem);
|
||||
|
|
|
@ -3,13 +3,14 @@ import ChannelThumbnail from 'component/channelThumbnail';
|
|||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
claim?: Claim,
|
||||
claimLabel?: string,
|
||||
claimTitle?: string,
|
||||
emote?: any,
|
||||
uri?: string,
|
||||
};
|
||||
|
||||
export default function TextareaSuggestionsItem(props: Props) {
|
||||
const { claim, emote, uri, ...autocompleteProps } = props;
|
||||
const { claimLabel, claimTitle, emote, uri, ...autocompleteProps } = props;
|
||||
|
||||
if (emote) {
|
||||
const { name: value, url, unicode } = emote;
|
||||
|
@ -18,8 +19,8 @@ export default function TextareaSuggestionsItem(props: Props) {
|
|||
<div {...autocompleteProps} dispatch={undefined}>
|
||||
{unicode ? <div className="emote">{unicode}</div> : <img className="emote" src={url} />}
|
||||
|
||||
<div className="textareaSuggestion__label">
|
||||
<span className="textareaSuggestion__title textareaSuggestion__value textareaSuggestion__value--emote">
|
||||
<div className="textarea-suggestion__label">
|
||||
<span className="textarea-suggestion__title textarea-suggestion__value textarea-suggestion__value--emote">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
|
@ -27,16 +28,16 @@ export default function TextareaSuggestionsItem(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
if (claim) {
|
||||
const value = claim.canonical_url.replace('lbry://', '').replace('#', ':');
|
||||
if (claimLabel) {
|
||||
const value = claimLabel;
|
||||
|
||||
return (
|
||||
<div {...autocompleteProps} dispatch={undefined}>
|
||||
<ChannelThumbnail xsmall uri={uri} />
|
||||
|
||||
<div className="textareaSuggestion__label">
|
||||
<span className="textareaSuggestion__title">{(claim.value && claim.value.title) || value}</span>
|
||||
<span className="textareaSuggestion__value">{value}</span>
|
||||
<div className="textarea-suggestion__label">
|
||||
<span className="textarea-suggestion__title">{claimTitle || value}</span>
|
||||
<span className="textarea-suggestion__value">{value}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -4,7 +4,6 @@ import { doSetMentionSearchResults } from 'redux/actions/search';
|
|||
import { makeSelectWinningUriForQuery } from 'redux/selectors/search';
|
||||
import { MAX_LIVESTREAM_COMMENTS } from 'constants/livestream';
|
||||
import { selectChannelMentionData } from 'redux/selectors/comments';
|
||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||
import { withRouter } from 'react-router';
|
||||
import TextareaWithSuggestions from './view';
|
||||
|
||||
|
@ -32,13 +31,12 @@ const select = (state, props) => {
|
|||
commentorUris,
|
||||
hasNewResolvedResults,
|
||||
searchQuery: query,
|
||||
showMature: selectShowMatureContent(state),
|
||||
};
|
||||
};
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
doResolveUris: (uris) => dispatch(doResolveUris(uris, true)),
|
||||
doSetMentionSearchResults: (query, uris) => dispatch(doSetMentionSearchResults(query, uris)),
|
||||
});
|
||||
const perform = {
|
||||
doResolveUris,
|
||||
doSetMentionSearchResults,
|
||||
};
|
||||
|
||||
export default withRouter(connect(select, perform)(TextareaWithSuggestions));
|
||||
|
|
33
ui/component/textareaWithSuggestions/render-group.jsx
Normal file
33
ui/component/textareaWithSuggestions/render-group.jsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
// @flow
|
||||
import LbcSymbol from 'component/common/lbc-symbol';
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
groupName: string,
|
||||
suggestionTerm?: ?string,
|
||||
searchQuery?: string,
|
||||
children: any,
|
||||
};
|
||||
|
||||
const TextareaSuggestionsGroup = (props: Props) => {
|
||||
const { groupName, suggestionTerm, searchQuery, children } = props;
|
||||
|
||||
return (
|
||||
<div key={groupName} className="textarea-suggestions__group">
|
||||
<label className="textarea-suggestions__group-label">
|
||||
{groupName === 'Top' ? (
|
||||
<LbcSymbol prefix={__('Winning Search for %matching_term%', { matching_term: searchQuery })} />
|
||||
) : suggestionTerm && suggestionTerm.length > 1 ? (
|
||||
__('%group_name% matching %matching_term%', { group_name: groupName, matching_term: suggestionTerm })
|
||||
) : (
|
||||
groupName
|
||||
)}
|
||||
</label>
|
||||
|
||||
{children}
|
||||
<hr className="textarea-suggestions__separator" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TextareaSuggestionsGroup;
|
51
ui/component/textareaWithSuggestions/render-input.jsx
Normal file
51
ui/component/textareaWithSuggestions/render-input.jsx
Normal file
|
@ -0,0 +1,51 @@
|
|||
// @flow
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import React from 'react';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Button from 'component/button';
|
||||
import Zoom from '@mui/material/Zoom';
|
||||
|
||||
type Props = {
|
||||
params: any,
|
||||
messageValue: string,
|
||||
inputDefaultProps: any,
|
||||
inputRef: any,
|
||||
handleEmojis: () => any,
|
||||
handleTip: (isLBC: boolean) => void,
|
||||
handleSubmit: () => any,
|
||||
};
|
||||
|
||||
const TextareaSuggestionsInput = (props: Props) => {
|
||||
const { params, messageValue, inputRef, inputDefaultProps, handleEmojis, handleTip, handleSubmit } = props;
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const { InputProps, disabled, fullWidth, id, inputProps: autocompleteInputProps } = params;
|
||||
const inputProps = { ...autocompleteInputProps, ...inputDefaultProps };
|
||||
const autocompleteProps = { InputProps, disabled, fullWidth, id, inputProps };
|
||||
|
||||
if (isMobile) {
|
||||
InputProps.startAdornment = <Button icon={ICONS.STICKER} onClick={handleEmojis} />;
|
||||
InputProps.endAdornment = (
|
||||
<>
|
||||
<Button icon={ICONS.LBC} onClick={() => handleTip(true)} />
|
||||
<Button icon={ICONS.FINANCE} onClick={() => handleTip(false)} />
|
||||
|
||||
<Zoom in={messageValue && messageValue.length > 0} mountOnEnter unmountOnExit>
|
||||
<div>
|
||||
<Button button="primary" icon={ICONS.SUBMIT} iconColor="red" onClick={() => handleSubmit()} />
|
||||
</div>
|
||||
</Zoom>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<TextField inputRef={inputRef} variant="outlined" multiline minRows={1} select={false} {...autocompleteProps} />
|
||||
);
|
||||
}
|
||||
|
||||
return <TextField inputRef={inputRef} multiline select={false} {...autocompleteProps} />;
|
||||
};
|
||||
|
||||
export default TextareaSuggestionsInput;
|
24
ui/component/textareaWithSuggestions/render-option.jsx
Normal file
24
ui/component/textareaWithSuggestions/render-option.jsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
// @flow
|
||||
import { EMOTES_48px as EMOTES } from 'constants/emotes';
|
||||
import EMOJIS from 'emoji-dictionary';
|
||||
import React from 'react';
|
||||
import TextareaSuggestionsItem from 'component/textareaSuggestionsItem';
|
||||
|
||||
type Props = {
|
||||
label: string,
|
||||
isEmote?: boolean,
|
||||
optionProps: any,
|
||||
};
|
||||
|
||||
const TextareaSuggestionsOption = (props: Props) => {
|
||||
const { label, isEmote, optionProps } = props;
|
||||
|
||||
const emoteFound = isEmote && EMOTES.find(({ name }) => name === label);
|
||||
const emoteValue = emoteFound ? { name: label, url: emoteFound.url } : undefined;
|
||||
const emojiFound = isEmote && EMOJIS.getUnicode(label);
|
||||
const emojiValue = emojiFound ? { name: label, unicode: emojiFound } : undefined;
|
||||
|
||||
return <TextareaSuggestionsItem key={label} uri={label} emote={emoteValue || emojiValue} {...optionProps} />;
|
||||
};
|
||||
|
||||
export default TextareaSuggestionsOption;
|
|
@ -2,22 +2,18 @@
|
|||
import { EMOTES_48px as EMOTES } from 'constants/emotes';
|
||||
import { matchSorter } from 'match-sorter';
|
||||
import { SEARCH_OPTIONS } from 'constants/search';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as KEYCODES from 'constants/keycodes';
|
||||
import Autocomplete from '@mui/material/Autocomplete';
|
||||
import BusyIndicator from 'component/common/busy-indicator';
|
||||
import EMOJIS from 'emoji-dictionary';
|
||||
import LbcSymbol from 'component/common/lbc-symbol';
|
||||
import Popper from '@mui/material/Popper';
|
||||
import React from 'react';
|
||||
import replaceAll from 'core-js-pure/features/string/replace-all';
|
||||
import TextareaSuggestionsItem from 'component/textareaSuggestionsItem';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import useLighthouse from 'effects/use-lighthouse';
|
||||
import useThrottle from 'effects/use-throttle';
|
||||
import { parseURI } from 'util/lbryURI';
|
||||
import Button from 'component/button';
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
import TextareaSuggestionsOption from './render-option';
|
||||
import TextareaSuggestionsInput from './render-input';
|
||||
import TextareaSuggestionsGroup from './render-group';
|
||||
|
||||
const SUGGESTION_REGEX = new RegExp(
|
||||
'((?:^| |\n)@[^\\s=&#$@%?:;/\\"<>%{}|^~[]*(?::[\\w]+)?)|((?:^| |\n):[\\w+-]*:?)',
|
||||
|
@ -57,12 +53,11 @@ type Props = {
|
|||
maxLength?: number,
|
||||
placeholder?: string,
|
||||
searchQuery?: string,
|
||||
showMature: boolean,
|
||||
type?: string,
|
||||
uri?: string,
|
||||
value: any,
|
||||
doResolveUris: (Array<string>) => void,
|
||||
doSetMentionSearchResults: (string, Array<string>) => void,
|
||||
doResolveUris: (uris: Array<string>, cache: boolean) => void,
|
||||
doSetMentionSearchResults: (query: string, uris: Array<string>) => void,
|
||||
onBlur: (any) => any,
|
||||
onChange: (any) => any,
|
||||
onFocus: (any) => any,
|
||||
|
@ -88,7 +83,6 @@ export default function TextareaWithSuggestions(props: Props) {
|
|||
maxLength,
|
||||
placeholder,
|
||||
searchQuery,
|
||||
showMature,
|
||||
type,
|
||||
value: messageValue,
|
||||
doResolveUris,
|
||||
|
@ -101,18 +95,15 @@ export default function TextareaWithSuggestions(props: Props) {
|
|||
handleSubmit,
|
||||
} = props;
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const inputDefaultProps = { className, placeholder, maxLength, type, disabled };
|
||||
|
||||
const [suggestionValue, setSuggestionValue] = React.useState(undefined);
|
||||
const [highlightedSuggestion, setHighlightedSuggestion] = React.useState('');
|
||||
const [shouldClose, setClose] = React.useState();
|
||||
const [debouncedTerm, setDebouncedTerm] = React.useState('');
|
||||
// const [mostSupported, setMostSupported] = React.useState('');
|
||||
|
||||
const suggestionTerm = suggestionValue && suggestionValue.term;
|
||||
const isEmote = suggestionValue && suggestionValue.isEmote;
|
||||
const isEmote = Boolean(suggestionValue && suggestionValue.isEmote);
|
||||
const isMention = suggestionValue && !suggestionValue.isEmote;
|
||||
|
||||
let invalidTerm = suggestionTerm && isMention && suggestionTerm.charAt(1) === ':';
|
||||
|
@ -125,7 +116,7 @@ export default function TextareaWithSuggestions(props: Props) {
|
|||
}
|
||||
|
||||
const additionalOptions = { isBackgroundSearch: false, [SEARCH_OPTIONS.CLAIM_TYPE]: SEARCH_OPTIONS.INCLUDE_CHANNELS };
|
||||
const { results, loading } = useLighthouse(debouncedTerm, showMature, SEARCH_SIZE, additionalOptions, 0);
|
||||
const { results, loading } = useLighthouse(debouncedTerm, false, SEARCH_SIZE, additionalOptions, 0);
|
||||
const stringifiedResults = JSON.stringify(results);
|
||||
|
||||
const hasMinLength = suggestionTerm && isMention && suggestionTerm.length >= LIGHTHOUSE_MIN_CHARACTERS;
|
||||
|
@ -180,7 +171,7 @@ export default function TextareaWithSuggestions(props: Props) {
|
|||
let emoteLabel;
|
||||
if (isEmote) {
|
||||
// $FlowFixMe
|
||||
emoteLabel = `:${replaceAll(option, ':', '')}:`;
|
||||
emoteLabel = `:${option.replace(/:/g, '')}:`;
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -316,14 +307,14 @@ export default function TextareaWithSuggestions(props: Props) {
|
|||
|
||||
const arrayResults = JSON.parse(stringifiedResults);
|
||||
if (debouncedTerm && arrayResults && arrayResults.length > 0) {
|
||||
doResolveUris([debouncedTerm, ...arrayResults]);
|
||||
doResolveUris([debouncedTerm, ...arrayResults], true);
|
||||
doSetMentionSearchResults(debouncedTerm, arrayResults);
|
||||
}
|
||||
}, [debouncedTerm, doResolveUris, doSetMentionSearchResults, stringifiedResults, suggestionTerm]);
|
||||
|
||||
// Only resolve commentors on Livestreams when first trying to mention/looking for it
|
||||
React.useEffect(() => {
|
||||
if (isLivestream && commentorUris && suggestionTerm) doResolveUris(commentorUris);
|
||||
if (isLivestream && commentorUris && suggestionTerm) doResolveUris(commentorUris, true);
|
||||
}, [commentorUris, doResolveUris, isLivestream, suggestionTerm]);
|
||||
|
||||
// Allow selecting with TAB key
|
||||
|
@ -371,58 +362,6 @@ export default function TextareaWithSuggestions(props: Props) {
|
|||
/** Render **/
|
||||
/** ------ **/
|
||||
|
||||
const renderGroup = (groupName: string, children: any) => (
|
||||
<div key={groupName} className="textareaSuggestions__group">
|
||||
<label className="textareaSuggestions__label">
|
||||
{groupName === 'Top' ? (
|
||||
<LbcSymbol prefix={__('Winning Search for %matching_term%', { matching_term: searchQuery })} />
|
||||
) : suggestionTerm && suggestionTerm.length > 1 ? (
|
||||
__('%group_name% matching %matching_term%', { group_name: groupName, matching_term: suggestionTerm })
|
||||
) : (
|
||||
groupName
|
||||
)}
|
||||
</label>
|
||||
{children}
|
||||
<hr className="textareaSuggestions__topSeparator" />
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderInput = (params: any) => {
|
||||
const { InputProps, disabled, fullWidth, id, inputProps: autocompleteInputProps } = params;
|
||||
|
||||
if (isMobile) {
|
||||
InputProps.startAdornment = <Button icon={ICONS.STICKER} onClick={handleEmojis} />;
|
||||
InputProps.endAdornment = (
|
||||
<>
|
||||
<Button icon={ICONS.LBC} onClick={() => handleTip(true)} />
|
||||
<Button icon={ICONS.FINANCE} onClick={() => handleTip(false)} />
|
||||
|
||||
{messageValue && messageValue.length > 0 && (
|
||||
<Button button="primary" icon={ICONS.SUBMIT} iconColor="red" onClick={() => handleSubmit()} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const inputProps = { ...autocompleteInputProps, ...inputDefaultProps };
|
||||
const autocompleteProps = { InputProps, disabled, fullWidth, id, inputProps };
|
||||
|
||||
return !isMobile ? (
|
||||
<TextField inputRef={inputRef} multiline select={false} {...autocompleteProps} />
|
||||
) : (
|
||||
<TextField inputRef={inputRef} variant="outlined" multiline minRows={1} select={false} {...autocompleteProps} />
|
||||
);
|
||||
};
|
||||
|
||||
const renderOption = (optionProps: any, label: string) => {
|
||||
const emoteFound = isEmote && EMOTES.find(({ name }) => name === label);
|
||||
const emoteValue = emoteFound ? { name: label, url: emoteFound.url } : undefined;
|
||||
const emojiFound = isEmote && EMOJIS.getUnicode(label);
|
||||
const emojiValue = emojiFound ? { name: label, unicode: emojiFound } : undefined;
|
||||
|
||||
return <TextareaSuggestionsItem key={label} uri={label} emote={emoteValue || emojiValue} {...optionProps} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
PopperComponent={AutocompletePopper}
|
||||
|
@ -450,16 +389,30 @@ export default function TextareaWithSuggestions(props: Props) {
|
|||
or else it will be displayed all the time as empty (no options) */
|
||||
open={!!suggestionTerm && !shouldClose}
|
||||
options={allOptionsGrouped}
|
||||
renderGroup={({ group, children }) => renderGroup(group, children)}
|
||||
renderInput={(params) => renderInput(params)}
|
||||
renderOption={(optionProps, option) => renderOption(optionProps, option.label)}
|
||||
renderGroup={({ group, children }) => (
|
||||
<TextareaSuggestionsGroup groupName={group} suggestionTerm={suggestionTerm} searchQuery={searchQuery}>
|
||||
{children}
|
||||
</TextareaSuggestionsGroup>
|
||||
)}
|
||||
renderInput={(params) => (
|
||||
<TextareaSuggestionsInput
|
||||
params={params}
|
||||
messageValue={messageValue}
|
||||
inputRef={inputRef}
|
||||
inputDefaultProps={inputDefaultProps}
|
||||
handleEmojis={handleEmojis}
|
||||
handleTip={handleTip}
|
||||
handleSubmit={handleSubmit}
|
||||
/>
|
||||
)}
|
||||
renderOption={(optionProps, option) => (
|
||||
<TextareaSuggestionsOption label={option.label} isEmote={isEmote} optionProps={optionProps} />
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function AutocompletePopper(props: any) {
|
||||
return <Popper {...props} placement="top" />;
|
||||
}
|
||||
const AutocompletePopper = (props: any) => <Popper {...props} placement="top" />;
|
||||
|
||||
function useSuggestionMatch(term: string, list: Array<string>) {
|
||||
const throttledTerm = useThrottle(term);
|
||||
|
|
|
@ -23,6 +23,15 @@ $thumbnailWidthSmall: 1rem;
|
|||
}
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
.commentCreate + .empty__wrap {
|
||||
p {
|
||||
font-size: var(--font-small);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.commentCreate--reply {
|
||||
margin-top: var(--spacing-m);
|
||||
position: relative;
|
||||
|
@ -41,7 +50,7 @@ $thumbnailWidthSmall: 1rem;
|
|||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.commentCreate__labelWrapper {
|
||||
.comment-create__label-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
@ -49,14 +58,18 @@ $thumbnailWidthSmall: 1rem;
|
|||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
|
||||
.commentCreate__label {
|
||||
.comment-create__label {
|
||||
white-space: nowrap;
|
||||
margin-right: var(--spacing-xs);
|
||||
}
|
||||
|
||||
@media (min-width: $breakpoint-small) {
|
||||
fieldset-section {
|
||||
max-width: 10rem;
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
fieldset-section {
|
||||
max-width: 10rem;
|
||||
font-size: var(--font-xxsmall);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -480,6 +480,12 @@ fieldset-group {
|
|||
@media (min-width: $breakpoint-small) {
|
||||
column-count: 2;
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
span {
|
||||
font-size: var(--font-xxsmall);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-field__quick-action {
|
||||
|
|
|
@ -25,9 +25,19 @@
|
|||
font-size: var(--font-xsmall) !important;
|
||||
flex-wrap: nowrap !important;
|
||||
color: var(--color-text) !important;
|
||||
padding: 0px 9px !important;
|
||||
|
||||
textarea {
|
||||
border: none;
|
||||
margin: 9px 0px;
|
||||
}
|
||||
|
||||
button:not(:first-of-type):not(:last-of-type) {
|
||||
margin: 0px var(--spacing-xxs);
|
||||
}
|
||||
|
||||
button + div {
|
||||
margin-left: var(--spacing-xxs);
|
||||
}
|
||||
|
||||
.button--primary {
|
||||
|
@ -52,12 +62,12 @@
|
|||
box-shadow: var(--card-box-shadow);
|
||||
color: var(--color-text) !important;
|
||||
|
||||
.textareaSuggestions__group {
|
||||
.textarea-suggestions__group {
|
||||
&:last-child hr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.textareaSuggestions__label {
|
||||
.textarea-suggestions__group-label {
|
||||
@extend .wunderbar__label;
|
||||
}
|
||||
|
||||
|
@ -97,23 +107,23 @@
|
|||
}
|
||||
}
|
||||
|
||||
.textareaSuggestion__label {
|
||||
.textarea-suggestion__label {
|
||||
@extend .wunderbar__suggestion-label;
|
||||
margin-left: var(--spacing-m);
|
||||
display: block;
|
||||
position: relative;
|
||||
|
||||
.textareaSuggestion__title {
|
||||
.textarea-suggestion__title {
|
||||
@extend .wunderbar__suggestion-title;
|
||||
}
|
||||
|
||||
.textareaSuggestion__value {
|
||||
.textarea-suggestion__value {
|
||||
@extend .wunderbar__suggestion-name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.textareaSuggestions__topSeparator {
|
||||
.textarea-suggestions__separator {
|
||||
@extend .wunderbar__top-separator;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue