Stop mouseUp propagation in statuses

This commit is contained in:
Alex Gleason 2022-11-19 14:36:58 -06:00
parent a0597a6445
commit e973d69c61
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
10 changed files with 269 additions and 259 deletions

View file

@ -9,6 +9,7 @@ import { getAcct } from 'soapbox/utils/accounts';
import { displayFqn } from 'soapbox/utils/state';
import RelativeTimestamp from './relative-timestamp';
import StopPropagation from './stop-propagation';
import { Avatar, Emoji, HStack, Icon, IconButton, Stack, Text } from './ui';
import type { Account as AccountEntity } from 'soapbox/types/entities';
@ -21,8 +22,6 @@ const InstanceFavicon: React.FC<IInstanceFavicon> = ({ account }) => {
const history = useHistory();
const handleClick: React.MouseEventHandler = (e) => {
e.stopPropagation();
const timelineUrl = `/timeline/${account.domain}`;
if (!(e.ctrlKey || e.metaKey)) {
history.push(timelineUrl);
@ -167,106 +166,100 @@ const Account = ({
const LinkEl: any = withLinkToProfile ? Link : 'div';
return (
<div data-testid='account' className='flex-shrink-0 group block w-full' ref={overflowRef}>
<HStack alignItems={actionAlignment} justifyContent='between'>
<HStack alignItems={withAccountNote ? 'top' : 'center'} space={3}>
<ProfilePopper
condition={showProfileHoverCard}
wrapper={(children) => <HoverRefWrapper className='relative' accountId={account.id} inline>{children}</HoverRefWrapper>}
>
<LinkEl
to={`/@${account.acct}`}
title={account.acct}
onClick={(event: React.MouseEvent) => event.stopPropagation()}
>
<Avatar src={account.avatar} size={avatarSize} />
{emoji && (
<Emoji
className='w-5 h-5 absolute -bottom-1.5 -right-1.5'
emoji={emoji}
/>
)}
</LinkEl>
</ProfilePopper>
<div className='flex-grow'>
<StopPropagation>
<div data-testid='account' className='flex-shrink-0 group block w-full' ref={overflowRef}>
<HStack alignItems={actionAlignment} justifyContent='between'>
<HStack alignItems={withAccountNote ? 'top' : 'center'} space={3}>
<ProfilePopper
condition={showProfileHoverCard}
wrapper={(children) => <HoverRefWrapper accountId={account.id} inline>{children}</HoverRefWrapper>}
wrapper={(children) => <HoverRefWrapper className='relative' accountId={account.id} inline>{children}</HoverRefWrapper>}
>
<LinkEl
to={`/@${account.acct}`}
title={account.acct}
onClick={(event: React.MouseEvent) => event.stopPropagation()}
>
<div className='flex items-center space-x-1 flex-grow' style={style}>
<Text
size='sm'
weight='semibold'
truncate
dangerouslySetInnerHTML={{ __html: account.display_name_html }}
<LinkEl to={`/@${account.acct}`} title={account.acct}>
<Avatar src={account.avatar} size={avatarSize} />
{emoji && (
<Emoji
className='w-5 h-5 absolute -bottom-1.5 -right-1.5'
emoji={emoji}
/>
{account.verified && <VerificationBadge />}
</div>
)}
</LinkEl>
</ProfilePopper>
<Stack space={withAccountNote ? 1 : 0}>
<HStack alignItems='center' space={1} style={style}>
<Text theme='muted' size='sm' truncate>@{username}</Text>
<div className='flex-grow'>
<ProfilePopper
condition={showProfileHoverCard}
wrapper={(children) => <HoverRefWrapper accountId={account.id} inline>{children}</HoverRefWrapper>}
>
<LinkEl to={`/@${account.acct}`} title={account.acct}>
<div className='flex items-center space-x-1 flex-grow' style={style}>
<Text
size='sm'
weight='semibold'
truncate
dangerouslySetInnerHTML={{ __html: account.display_name_html }}
/>
{account.favicon && (
<InstanceFavicon account={account} />
)}
{account.verified && <VerificationBadge />}
</div>
</LinkEl>
</ProfilePopper>
{(timestamp) ? (
<>
<Text tag='span' theme='muted' size='sm'>&middot;</Text>
<Stack space={withAccountNote ? 1 : 0}>
<HStack alignItems='center' space={1} style={style}>
<Text theme='muted' size='sm' truncate>@{username}</Text>
{timestampUrl ? (
<Link to={timestampUrl} className='hover:underline' onClick={(event) => event.stopPropagation()}>
{account.favicon && (
<InstanceFavicon account={account} />
)}
{(timestamp) ? (
<>
<Text tag='span' theme='muted' size='sm'>&middot;</Text>
{timestampUrl ? (
<Link to={timestampUrl} className='hover:underline'>
<RelativeTimestamp timestamp={timestamp} theme='muted' size='sm' className='whitespace-nowrap' futureDate={futureTimestamp} />
</Link>
) : (
<RelativeTimestamp timestamp={timestamp} theme='muted' size='sm' className='whitespace-nowrap' futureDate={futureTimestamp} />
</Link>
) : (
<RelativeTimestamp timestamp={timestamp} theme='muted' size='sm' className='whitespace-nowrap' futureDate={futureTimestamp} />
)}
</>
) : null}
)}
</>
) : null}
{showEdit ? (
<>
<Text tag='span' theme='muted' size='sm'>&middot;</Text>
{showEdit ? (
<>
<Text tag='span' theme='muted' size='sm'>&middot;</Text>
<Icon className='h-5 w-5 text-gray-700 dark:text-gray-600' src={require('@tabler/icons/pencil.svg')} />
</>
) : null}
<Icon className='h-5 w-5 text-gray-700 dark:text-gray-600' src={require('@tabler/icons/pencil.svg')} />
</>
) : null}
{actionType === 'muting' && account.mute_expires_at ? (
<>
<Text tag='span' theme='muted' size='sm'>&middot;</Text>
{actionType === 'muting' && account.mute_expires_at ? (
<>
<Text tag='span' theme='muted' size='sm'>&middot;</Text>
<Text theme='muted' size='sm'><RelativeTimestamp timestamp={account.mute_expires_at} futureDate /></Text>
</>
) : null}
</HStack>
<Text theme='muted' size='sm'><RelativeTimestamp timestamp={account.mute_expires_at} futureDate /></Text>
</>
) : null}
</HStack>
{withAccountNote && (
<Text
size='sm'
dangerouslySetInnerHTML={{ __html: account.note_emojified }}
className='mr-2'
/>
)}
</Stack>
{withAccountNote && (
<Text
size='sm'
dangerouslySetInnerHTML={{ __html: account.note_emojified }}
className='mr-2'
/>
)}
</Stack>
</div>
</HStack>
<div ref={actionRef}>
{withRelationship ? renderAction() : null}
</div>
</HStack>
<div ref={actionRef}>
{withRelationship ? renderAction() : null}
</div>
</HStack>
</div>
</div>
</StopPropagation>
);
};

View file

@ -5,6 +5,7 @@ import { openModal } from 'soapbox/actions/modals';
import { vote } from 'soapbox/actions/polls';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import StopPropagation from '../stop-propagation';
import { Stack, Text } from '../ui';
import PollFooter from './poll-footer';
@ -64,8 +65,7 @@ const Poll: React.FC<IPoll> = ({ id, status }): JSX.Element | null => {
const showResults = poll.voted || poll.expired;
return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div onClick={e => e.stopPropagation()}>
<StopPropagation>
{!showResults && poll.multiple && (
<Text theme='muted' size='sm'>
{intl.formatMessage(messages.multiple)}
@ -93,7 +93,7 @@ const Poll: React.FC<IPoll> = ({ id, status }): JSX.Element | null => {
selected={selected}
/>
</Stack>
</div>
</StopPropagation>
);
};

View file

@ -13,6 +13,7 @@ import OutlineBox from './outline-box';
import StatusContent from './status-content';
import StatusReplyMentions from './status-reply-mentions';
import SensitiveContentOverlay from './statuses/sensitive-content-overlay';
import StopPropagation from './stop-propagation';
import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities';
@ -91,58 +92,60 @@ const QuotedStatus: React.FC<IQuotedStatus> = ({ status, onCancel, compose }) =>
}
return (
<OutlineBox
data-testid='quoted-status'
className={classNames('cursor-pointer', {
'hover:bg-gray-100 dark:hover:bg-gray-800': !compose,
})}
>
<Stack
space={2}
onClick={handleExpandClick}
<StopPropagation>
<OutlineBox
data-testid='quoted-status'
className={classNames('cursor-pointer', {
'hover:bg-gray-100 dark:hover:bg-gray-800': !compose,
})}
>
<AccountContainer
{...actions}
id={account.id}
timestamp={status.created_at}
withRelationship={false}
showProfileHoverCard={!compose}
withLinkToProfile={!compose}
/>
<StatusReplyMentions status={status} hoverable={false} />
<Stack
className='relative z-0'
style={{ minHeight: status.hidden ? Math.max(minHeight, 208) + 12 : undefined }}
space={2}
onClick={handleExpandClick}
>
{(status.hidden) && (
<SensitiveContentOverlay
status={status}
visible={showMedia}
onToggleVisibility={handleToggleMediaVisibility}
ref={overlay}
/>
)}
<AccountContainer
{...actions}
id={account.id}
timestamp={status.created_at}
withRelationship={false}
showProfileHoverCard={!compose}
withLinkToProfile={!compose}
/>
<Stack space={4}>
<StatusContent
status={status}
collapsable
/>
<StatusReplyMentions status={status} hoverable={false} />
{(status.card || status.media_attachments.size > 0) && (
<StatusMedia
<Stack
className='relative z-0'
style={{ minHeight: status.hidden ? Math.max(minHeight, 208) + 12 : undefined }}
>
{(status.hidden) && (
<SensitiveContentOverlay
status={status}
muted={compose}
showMedia={showMedia}
visible={showMedia}
onToggleVisibility={handleToggleMediaVisibility}
ref={overlay}
/>
)}
<Stack space={4}>
<StatusContent
status={status}
collapsable
/>
{(status.card || status.media_attachments.size > 0) && (
<StatusMedia
status={status}
muted={compose}
showMedia={showMedia}
onToggleVisibility={handleToggleMediaVisibility}
/>
)}
</Stack>
</Stack>
</Stack>
</Stack>
</OutlineBox>
</OutlineBox>
</StopPropagation>
);
};

View file

@ -1,4 +1,3 @@
import classNames from 'clsx';
import { List as ImmutableList } from 'immutable';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
@ -16,6 +15,7 @@ import { initReport } from 'soapbox/actions/reports';
import { deleteStatus, editStatus, toggleMuteStatus } from 'soapbox/actions/statuses';
import EmojiButtonWrapper from 'soapbox/components/emoji-button-wrapper';
import StatusActionButton from 'soapbox/components/status-action-button';
import { HStack } from 'soapbox/components/ui';
import DropdownMenuContainer from 'soapbox/containers/dropdown-menu-container';
import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount, useSettings, useSoapboxConfig } from 'soapbox/hooks';
import { isLocal } from 'soapbox/utils/accounts';
@ -127,8 +127,6 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
} else {
onOpenUnauthorizedModal('REPLY');
}
e.stopPropagation();
};
const handleShareClick = () => {
@ -146,18 +144,13 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
} else {
onOpenUnauthorizedModal('FAVOURITE');
}
e.stopPropagation();
};
const handleBookmarkClick: React.EventHandler<React.MouseEvent> = (e) => {
e.stopPropagation();
dispatch(toggleBookmark(status));
};
const handleReblogClick: React.EventHandler<React.MouseEvent> = e => {
e.stopPropagation();
if (me) {
const modalReblog = () => dispatch(toggleReblog(status));
const boostModal = settings.get('boostModal');
@ -172,8 +165,6 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
};
const handleQuoteClick: React.EventHandler<React.MouseEvent> = (e) => {
e.stopPropagation();
if (me) {
dispatch(quoteCompose(status));
} else {
@ -199,12 +190,10 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
};
const handleDeleteClick: React.EventHandler<React.MouseEvent> = (e) => {
e.stopPropagation();
doDeleteStatus();
};
const handleRedraftClick: React.EventHandler<React.MouseEvent> = (e) => {
e.stopPropagation();
doDeleteStatus(true);
};
@ -213,35 +202,29 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
};
const handlePinClick: React.EventHandler<React.MouseEvent> = (e) => {
e.stopPropagation();
dispatch(togglePin(status));
};
const handleMentionClick: React.EventHandler<React.MouseEvent> = (e) => {
e.stopPropagation();
dispatch(mentionCompose(status.account as Account));
};
const handleDirectClick: React.EventHandler<React.MouseEvent> = (e) => {
e.stopPropagation();
dispatch(directCompose(status.account as Account));
};
const handleChatClick: React.EventHandler<React.MouseEvent> = (e) => {
e.stopPropagation();
const account = status.account as Account;
dispatch(launchChat(account.id, history));
};
const handleMuteClick: React.EventHandler<React.MouseEvent> = (e) => {
e.stopPropagation();
dispatch(initMuteModal(status.account as Account));
};
const handleBlockClick: React.EventHandler<React.MouseEvent> = (e) => {
e.stopPropagation();
const account = status.get('account') as Account;
dispatch(openModal('CONFIRM', {
icon: require('@tabler/icons/ban.svg'),
heading: <FormattedMessage id='confirmations.block.heading' defaultMessage='Block @{name}' values={{ name: account.get('acct') }} />,
@ -257,7 +240,6 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
};
const handleOpen: React.EventHandler<React.MouseEvent> = (e) => {
e.stopPropagation();
history.push(`/@${status.getIn(['account', 'acct'])}/posts/${status.id}`);
};
@ -269,12 +251,10 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
};
const handleReport: React.EventHandler<React.MouseEvent> = (e) => {
e.stopPropagation();
dispatch(initReport(status.account as Account, status));
};
const handleConversationMuteClick: React.EventHandler<React.MouseEvent> = (e) => {
e.stopPropagation();
dispatch(toggleMuteStatus(status));
};
@ -282,8 +262,6 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
const { uri } = status;
const textarea = document.createElement('textarea');
e.stopPropagation();
textarea.textContent = uri;
textarea.style.position = 'fixed';
@ -300,18 +278,15 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
};
const onModerate: React.MouseEventHandler = (e) => {
e.stopPropagation();
const account = status.account as Account;
dispatch(openModal('ACCOUNT_MODERATION', { accountId: account.id }));
};
const handleDeleteStatus: React.EventHandler<React.MouseEvent> = (e) => {
e.stopPropagation();
dispatch(deleteStatusModal(intl, status.id));
};
const handleToggleStatusSensitivity: React.EventHandler<React.MouseEvent> = (e) => {
e.stopPropagation();
dispatch(toggleStatusSensitivityModal(intl, status.id, status.sensitive));
};
@ -550,74 +525,77 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
const canShare = ('share' in navigator) && status.visibility === 'public';
return (
<div
data-testid='status-action-bar'
className={classNames('flex flex-row', {
'justify-between': space === 'expand',
'space-x-2': space === 'compact',
})}
>
<StatusActionButton
title={replyTitle}
icon={require('@tabler/icons/message-circle-2.svg')}
onClick={handleReplyClick}
count={replyCount}
text={withLabels ? intl.formatMessage(messages.reply) : undefined}
/>
<HStack data-testid='status-action-bar'>
<HStack
justifyContent={space === 'expand' ? 'between' : undefined}
space={space === 'compact' ? 2 : undefined}
grow={space === 'expand'}
onMouseUp={e => e.stopPropagation()}
onMouseDown={e => e.stopPropagation()}
onClick={e => e.stopPropagation()}
>
<StatusActionButton
title={replyTitle}
icon={require('@tabler/icons/message-circle-2.svg')}
onClick={handleReplyClick}
count={replyCount}
text={withLabels ? intl.formatMessage(messages.reply) : undefined}
/>
{(features.quotePosts && me) ? (
<DropdownMenuContainer
items={reblogMenu}
disabled={!publicStatus}
onShiftClick={handleReblogClick}
>
{reblogButton}
</DropdownMenuContainer>
) : (
reblogButton
)}
{(features.quotePosts && me) ? (
<DropdownMenuContainer
items={reblogMenu}
disabled={!publicStatus}
onShiftClick={handleReblogClick}
>
{reblogButton}
</DropdownMenuContainer>
) : (
reblogButton
)}
{features.emojiReacts ? (
<EmojiButtonWrapper statusId={status.id}>
{features.emojiReacts ? (
<EmojiButtonWrapper statusId={status.id}>
<StatusActionButton
title={meEmojiTitle}
icon={require('@tabler/icons/heart.svg')}
filled
color='accent'
active={Boolean(meEmojiReact)}
count={emojiReactCount}
emoji={meEmojiReact}
text={withLabels ? meEmojiTitle : undefined}
/>
</EmojiButtonWrapper>
) : (
<StatusActionButton
title={meEmojiTitle}
title={intl.formatMessage(messages.favourite)}
icon={require('@tabler/icons/heart.svg')}
filled
color='accent'
filled
onClick={handleFavouriteClick}
active={Boolean(meEmojiReact)}
count={emojiReactCount}
emoji={meEmojiReact}
count={favouriteCount}
text={withLabels ? meEmojiTitle : undefined}
/>
</EmojiButtonWrapper>
) : (
<StatusActionButton
title={intl.formatMessage(messages.favourite)}
icon={require('@tabler/icons/heart.svg')}
color='accent'
filled
onClick={handleFavouriteClick}
active={Boolean(meEmojiReact)}
count={favouriteCount}
text={withLabels ? meEmojiTitle : undefined}
/>
)}
)}
{canShare && (
<StatusActionButton
title={intl.formatMessage(messages.share)}
icon={require('@tabler/icons/upload.svg')}
onClick={handleShareClick}
/>
)}
{canShare && (
<StatusActionButton
title={intl.formatMessage(messages.share)}
icon={require('@tabler/icons/upload.svg')}
onClick={handleShareClick}
/>
)}
<DropdownMenuContainer items={menu} status={status}>
<StatusActionButton
title={intl.formatMessage(messages.more)}
icon={require('@tabler/icons/dots.svg')}
/>
</DropdownMenuContainer>
</div>
<DropdownMenuContainer items={menu} status={status}>
<StatusActionButton
title={intl.formatMessage(messages.more)}
icon={require('@tabler/icons/dots.svg')}
/>
</DropdownMenuContainer>
</HStack>
</HStack>
);
};

View file

@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { openModal } from 'soapbox/actions/modals';
import AttachmentThumbs from 'soapbox/components/attachment-thumbs';
import StopPropagation from 'soapbox/components/stop-propagation';
import PlaceholderCard from 'soapbox/features/placeholder/components/placeholder-card';
import Card from 'soapbox/features/status/components/card';
import Bundle from 'soapbox/features/ui/components/bundle';
@ -173,7 +174,15 @@ const StatusMedia: React.FC<IStatusMedia> = ({
);
}
return media;
if (media) {
return (
<StopPropagation>
{media}
</StopPropagation>
);
} else {
return null;
}
};
export default StatusMedia;

View file

@ -235,7 +235,8 @@ const Status: React.FC<IStatus> = (props) => {
reblogElement = (
<NavLink
to={`/@${status.getIn(['account', 'acct'])}`}
onClick={(event) => event.stopPropagation()}
onClick={e => e.stopPropagation()}
onMouseUp={e => e.stopPropagation()}
className='hidden sm:flex items-center text-gray-700 dark:text-gray-600 text-xs font-medium space-x-1 hover:underline'
>
<Icon src={require('@tabler/icons/repeat.svg')} className='text-green-600' />
@ -258,7 +259,8 @@ const Status: React.FC<IStatus> = (props) => {
<div className='pb-5 -mt-2 sm:hidden truncate'>
<NavLink
to={`/@${status.getIn(['account', 'acct'])}`}
onClick={(event) => event.stopPropagation()}
onClick={e => e.stopPropagation()}
onMouseUp={e => e.stopPropagation()}
className='flex items-center text-gray-700 dark:text-gray-600 text-xs font-medium space-x-1 hover:underline'
>
<Icon src={require('@tabler/icons/repeat.svg')} className='text-green-600' />

View file

@ -5,6 +5,7 @@ import { defineMessages, useIntl } from 'react-intl';
import { useSettings, useSoapboxConfig } from 'soapbox/hooks';
import { defaultMediaVisibility } from 'soapbox/utils/status';
import StopPropagation from '../stop-propagation';
import { Button, HStack, Text } from '../ui';
import type { Status as StatusEntity } from 'soapbox/types/entities';
@ -38,9 +39,7 @@ const SensitiveContentOverlay = React.forwardRef<HTMLDivElement, ISensitiveConte
const [visible, setVisible] = useState<boolean>(defaultMediaVisibility(status, displayMedia));
const toggleVisibility = (event: React.MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();
const toggleVisibility = () => {
if (onToggleVisibility) {
onToggleVisibility();
} else {
@ -64,13 +63,15 @@ const SensitiveContentOverlay = React.forwardRef<HTMLDivElement, ISensitiveConte
data-testid='sensitive-overlay'
>
{visible ? (
<Button
text={intl.formatMessage(messages.hide)}
icon={require('@tabler/icons/eye-off.svg')}
onClick={toggleVisibility}
theme='primary'
size='sm'
/>
<StopPropagation>
<Button
text={intl.formatMessage(messages.hide)}
icon={require('@tabler/icons/eye-off.svg')}
onClick={toggleVisibility}
theme='primary'
size='sm'
/>
</StopPropagation>
) : (
<div className='text-center w-3/4 mx-auto space-y-4' ref={ref}>
<div className='space-y-1'>
@ -92,36 +93,34 @@ const SensitiveContentOverlay = React.forwardRef<HTMLDivElement, ISensitiveConte
</div>
<HStack alignItems='center' justifyContent='center' space={2}>
{isUnderReview ? (
<>
{links.get('support') && (
<a
href={links.get('support')}
target='_blank'
onClick={(event) => event.stopPropagation()}
>
<Button
type='button'
theme='outline'
size='sm'
icon={require('@tabler/icons/headset.svg')}
>
{intl.formatMessage(messages.contact)}
</Button>
</a>
)}
</>
) : null}
<StopPropagation>
{isUnderReview ? (
<>
{links.get('support') && (
<a href={links.get('support')} target='_blank'>
<Button
type='button'
theme='outline'
size='sm'
icon={require('@tabler/icons/headset.svg')}
>
{intl.formatMessage(messages.contact)}
</Button>
</a>
)}
</>
) : null}
<Button
type='button'
theme='outline'
size='sm'
icon={require('@tabler/icons/eye.svg')}
onClick={toggleVisibility}
>
{intl.formatMessage(messages.show)}
</Button>
<Button
type='button'
theme='outline'
size='sm'
icon={require('@tabler/icons/eye.svg')}
onClick={toggleVisibility}
>
{intl.formatMessage(messages.show)}
</Button>
</StopPropagation>
</HStack>
</div>
)}

View file

@ -0,0 +1,28 @@
import React from 'react';
interface IStopPropagation {
children: React.ReactNode,
}
/**
* Prevent mouse events from bubbling up.
*
* Why is this needed? Because `onClick`, `onMouseDown`, and `onMouseUp` are 3 separate events.
* To prevent a lot of code duplication, this component can stop all mouse events.
* Plus, placing it in the component tree makes it more readable.
*/
const StopPropagation: React.FC<IStopPropagation> = ({ children }) => {
const handler: React.MouseEventHandler<HTMLDivElement> = (e) => {
e.stopPropagation();
};
return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div onClick={handler} onMouseDown={handler} onMouseUp={handler}>
{children}
</div>
);
};
export default StopPropagation;

View file

@ -8,7 +8,7 @@ import { useButtonStyles } from './useButtonStyles';
import type { ButtonSizes, ButtonThemes } from './useButtonStyles';
interface IButton {
interface IButton extends Pick<React.HTMLAttributes<HTMLButtonElement>, 'onClick' | 'onMouseUp'> {
/** Whether this button expands the width of its container. */
block?: boolean,
/** Elements inside the <button> */
@ -19,8 +19,6 @@ interface IButton {
disabled?: boolean,
/** URL to an SVG icon to render inside the button. */
icon?: string,
/** Action when the button is clicked. */
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void,
/** A predefined button size. */
size?: ButtonSizes,
/** Text inside the button. Takes precedence over `children`. */

View file

@ -27,7 +27,7 @@ const spaces = {
8: 'space-x-8',
};
interface IHStack {
interface IHStack extends Pick<React.HTMLAttributes<HTMLDivElement>, 'onClick' | 'onMouseUp' | 'onMouseDown'> {
/** Vertical alignment of children. */
alignItems?: keyof typeof alignItemsOptions
/** Extra class names on the <div> element. */