diff --git a/stylesheets/components/AudioCapture.scss b/stylesheets/components/AudioCapture.scss index e0c3cfa58d..703ba20715 100644 --- a/stylesheets/components/AudioCapture.scss +++ b/stylesheets/components/AudioCapture.scss @@ -13,90 +13,6 @@ align-items: center; background: none; - &__microphone { - height: 32px; - width: 32px; - border-radius: 4px; - text-align: center; - background: none; - display: flex; - align-items: center; - justify-content: center; - - padding: 0; - border: none; - - @include mixins.keyboard-mode { - &:focus { - outline: 2px solid variables.$color-ultramarine; - } - } - - outline: none; - - &:before { - content: ''; - display: inline-block; - height: 20px; - width: 20px; - - @include mixins.color-svg( - '../images/icons/v3/mic/mic.svg', - var(--color-label-primary) - ); - } - } - - &__recorder-button { - flex-grow: 0; - flex-shrink: 0; - - width: 32px; - height: 32px; - border-radius: 32px; - opacity: 0.3; - text-align: center; - padding: 0; - - &:focus, - &:hover { - opacity: 1; - } - - outline: none; - - .icon { - display: inline-block; - width: 24px; - height: 24px; - margin-bottom: -3px; - } - - &--complete { - background: color.adjust(variables.$color-accent-green, $lightness: 20%); - border: 1px solid variables.$color-accent-green; - - .icon { - @include mixins.color-svg( - '../images/icons/v3/check/check.svg', - variables.$color-accent-green - ); - } - } - - &--cancel { - background: color.adjust(variables.$color-accent-red, $lightness: 20%); - border: 1px solid variables.$color-accent-red; - - .icon { - @include mixins.color-svg( - '../images/icons/v3/x/x.svg', - variables.$color-accent-red - ); - } - } - } - &__time { color: variables.$color-gray-60; font-variant: tabular-nums; diff --git a/stylesheets/components/CompositionArea.scss b/stylesheets/components/CompositionArea.scss index 88fb669cbd..5ceb70b3cf 100644 --- a/stylesheets/components/CompositionArea.scss +++ b/stylesheets/components/CompositionArea.scss @@ -99,26 +99,6 @@ } } - &__send-button { - display: flex; - justify-content: center; - align-items: center; - background: none; - border: none; - width: 32px; - height: 32px; - &::after { - display: block; - content: ''; - width: 20px; - height: 20px; - flex-shrink: 0; - @include mixins.color-svg( - '../images/icons/v3/send/send-fill.svg', - variables.$color-ultramarine - ); - } - } &__input { flex-grow: 1; position: relative; diff --git a/stylesheets/components/ForwardMessageModal.scss b/stylesheets/components/ForwardMessageModal.scss index 357b1c9350..bd119a87d8 100644 --- a/stylesheets/components/ForwardMessageModal.scss +++ b/stylesheets/components/ForwardMessageModal.scss @@ -44,44 +44,6 @@ justify-content: center; } - &__send-button { - align-items: center; - border: none; - border-radius: 100%; - display: flex; - height: 32px; - justify-content: center; - width: 32px; - - &::after { - content: ''; - display: block; - flex-shrink: 0; - height: 20px; - width: 20px; - } - - &--continue { - &::after { - height: 24px; - width: 24px; - @include mixins.color-svg( - '../images/icons/v3/arrow/arrow-right.svg', - variables.$color-white - ); - } - } - - &--forward { - &::after { - @include mixins.color-svg( - '../images/icons/v3/send/send-fill.svg', - variables.$color-white - ); - } - } - } - // Disable vertical scrolling on the modal pages // since the elements inside are scrollable themselves .module-Modal__body { diff --git a/stylesheets/components/MediaQualitySelector.scss b/stylesheets/components/MediaQualitySelector.scss deleted file mode 100644 index 36395055ec..0000000000 --- a/stylesheets/components/MediaQualitySelector.scss +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2021 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -@use '../mixins'; -@use '../variables'; - -.MediaQualitySelector { - &__popper { - @include mixins.module-composition-popper; - & { - color: var(--color-label-primary); - padding-block: 12px; - padding-inline: 16px; - width: auto; - } - } - - &__title { - @include mixins.font-body-1-bold; - margin-bottom: 12px; - } - - &__option { - @include mixins.button-reset(); - - & { - align-items: center; - border-radius: 6px; - display: flex; - height: 42px; - margin-block: 2px; - margin-inline: 0; - min-width: 200px; - } - - &--checkmark { - height: 12px; - margin-block: 0; - margin-inline: 6px; - width: 16px; - } - - &--selected { - @include mixins.color-svg( - '../images/icons/v3/check/check-compact.svg', - currentColor - ); - } - - &--title { - @include mixins.font-body-2; - } - - &--description { - @include mixins.font-subtitle; - } - - &:hover { - @include mixins.light-theme() { - background-color: variables.$color-gray-05; - } - - @include mixins.dark-theme() { - background-color: variables.$color-gray-65; - } - } - - &:focus { - outline: none; - } - - &:focus-visible { - box-shadow: 0 0 1px 1px variables.$color-ultramarine; - } - } -} diff --git a/stylesheets/components/RecordingComposer.scss b/stylesheets/components/RecordingComposer.scss index bab62fc92c..f341aad766 100644 --- a/stylesheets/components/RecordingComposer.scss +++ b/stylesheets/components/RecordingComposer.scss @@ -29,12 +29,4 @@ background: variables.$color-gray-75; } } - - &__button { - font-size: 13px; - min-width: 76px; - line-height: 18px; - padding-block: 5px; - padding-inline: 16px; - } } diff --git a/stylesheets/components/fun/FunButton.scss b/stylesheets/components/fun/FunButton.scss index b4519c54f2..c01b0c5215 100644 --- a/stylesheets/components/fun/FunButton.scss +++ b/stylesheets/components/fun/FunButton.scss @@ -35,7 +35,6 @@ flex-shrink: 0; } -.FunButton__Icon--FunPicker, .FunButton__Icon--EmojiPicker { @include mixins.color-svg( '../images/icons/v3/emoji/emoji.svg', diff --git a/stylesheets/manifest.scss b/stylesheets/manifest.scss index ecc872a912..37b7066cd5 100644 --- a/stylesheets/manifest.scss +++ b/stylesheets/manifest.scss @@ -128,7 +128,6 @@ $is-storybook: false !default; @use 'components/ListTile.scss'; @use 'components/LowDiskSpaceBackupImportModal.scss'; @use 'components/MediaEditor.scss'; -@use 'components/MediaQualitySelector.scss'; @use 'components/MessageAudio.scss'; @use 'components/MessageBody.scss'; @use 'components/MessageTextRenderer.scss'; diff --git a/ts/axo/AxoIconButton.dom.tsx b/ts/axo/AxoIconButton.dom.tsx index f92f43d6f4..5843287689 100644 --- a/ts/axo/AxoIconButton.dom.tsx +++ b/ts/axo/AxoIconButton.dom.tsx @@ -1,7 +1,6 @@ // Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only - -import type { FC, Ref, MouseEvent } from 'react'; +import type { FC, Ref, MouseEvent, FocusEvent } from 'react'; import { memo, useCallback, useMemo } from 'react'; import { AxoSymbol } from './AxoSymbol.dom.tsx'; import { tw } from './tw.dom.tsx'; @@ -174,6 +173,14 @@ export namespace AxoIconButton { * Called when the button is clicked. Not called when `pending` or `disabled`. */ onClick?: (event: MouseEvent) => void; + /** + * Called when the mouse enters the button. + */ + onMouseEnter?: (event: MouseEvent) => void; + /** + * Called when the mouse focuses the button. + */ + onFocus?: (event: FocusEvent) => void; }>; /** @@ -215,6 +222,8 @@ export namespace AxoIconButton { pressed, disabled, onClick, + onMouseEnter, + onFocus, ...rest } = props; const intl = useAxoIntl(); @@ -249,6 +258,8 @@ export namespace AxoIconButton { aria-pressed={pressed ?? undefined} aria-disabled={(pending || disabled) ?? undefined} onClick={handleClick} + onMouseEnter={onMouseEnter} + onFocus={onFocus} className={tw(baseStyles, Variants.get(variant), Sizes.get(size))} {...rest} > diff --git a/ts/components/CompositionArea.dom.tsx b/ts/components/CompositionArea.dom.tsx index 552c03152b..eae2538470 100644 --- a/ts/components/CompositionArea.dom.tsx +++ b/ts/components/CompositionArea.dom.tsx @@ -870,11 +870,12 @@ export const CompositionArea = memo(function CompositionArea({
-
@@ -1294,11 +1295,12 @@ export const CompositionArea = memo(function CompositionArea({ {isViewOnceActive && (
-
diff --git a/ts/components/ForwardMessagesModal.dom.tsx b/ts/components/ForwardMessagesModal.dom.tsx index 3e0d80f895..2959193875 100644 --- a/ts/components/ForwardMessagesModal.dom.tsx +++ b/ts/components/ForwardMessagesModal.dom.tsx @@ -12,7 +12,6 @@ import { } from 'react'; import { AttachmentList } from './conversation/AttachmentList.dom.tsx'; import type { AttachmentForUIType } from '../types/Attachment.std.ts'; -import { Button } from './Button.dom.tsx'; import { ContactCheckboxDisabledReason } from './conversationList/ContactCheckbox.dom.tsx'; import type { Row } from './ConversationList.dom.tsx'; import { ConversationList, RowType } from './ConversationList.dom.tsx'; @@ -44,6 +43,7 @@ import { missingCaseError } from '../util/missingCaseError.std.ts'; import { Theme } from '../util/theme.std.ts'; import { Emoji } from '../axo/emoji.std.ts'; import { AxoConfirmDialog } from '../axo/AxoConfirmDialog.dom.tsx'; +import { AxoIconButton } from '../axo/AxoIconButton.dom.tsx'; export enum ForwardMessagesModalType { Forward, @@ -260,16 +260,20 @@ export function ForwardMessagesModal({
{isEditingMessage || !isLonelyDraftEditable ? ( -
)} diff --git a/ts/components/MediaQualitySelector.dom.stories.tsx b/ts/components/MediaQualitySelector.dom.stories.tsx index 653f24e0ee..6e3a339156 100644 --- a/ts/components/MediaQualitySelector.dom.stories.tsx +++ b/ts/components/MediaQualitySelector.dom.stories.tsx @@ -5,18 +5,20 @@ import type { JSX } from 'react'; import { action } from '@storybook/addon-actions'; import type { Meta } from '@storybook/react'; -import type { PropsType } from './MediaQualitySelector.dom.tsx'; +import type { MediaQualitySelectorProps } from './MediaQualitySelector.dom.tsx'; import { MediaQualitySelector } from './MediaQualitySelector.dom.tsx'; export default { title: 'Components/MediaQualitySelector', argTypes: {}, args: {}, -} satisfies Meta; +} satisfies Meta; const { i18n } = window.SignalContext; -const createProps = (overrideProps: Partial = {}): PropsType => ({ +const createProps = ( + overrideProps: Partial = {} +): MediaQualitySelectorProps => ({ conversationId: 'abc123', i18n, isHighQuality: overrideProps.isHighQuality ?? false, diff --git a/ts/components/MediaQualitySelector.dom.tsx b/ts/components/MediaQualitySelector.dom.tsx index 3d4e2d56c3..9e92e9a401 100644 --- a/ts/components/MediaQualitySelector.dom.tsx +++ b/ts/components/MediaQualitySelector.dom.tsx @@ -1,60 +1,45 @@ // Copyright 2018 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { KeyboardEvent, JSX } from 'react'; -import { useCallback, useRef, useState } from 'react'; -import classNames from 'classnames'; -import { Popover } from 'radix-ui'; +import type { JSX } from 'react'; +import { useMemo, useCallback } from 'react'; import type { LocalizerType } from '../types/Util.std.ts'; -import { ThemeType } from '../types/Util.std.ts'; import { AxoIconButton } from '../axo/AxoIconButton.dom.tsx'; +import { AxoDropdownMenu } from '../axo/AxoDropdownMenu.dom.tsx'; +import { tw } from '../axo/tw.dom.tsx'; -export type PropsType = { +export type MediaQualitySelectorProps = Readonly<{ conversationId: string; i18n: LocalizerType; isHighQuality: boolean; - onSelectQuality: (conversationId: string, isHQ: boolean) => unknown; - theme?: ThemeType; -}; + onSelectQuality: (conversationId: string, isHighQuality: boolean) => unknown; +}>; + +enum MediaQuality { + Standard = 'standard', + High = 'high', +} export function MediaQualitySelector({ conversationId, i18n, isHighQuality, onSelectQuality, - theme, -}: PropsType): JSX.Element { - const [open, setOpen] = useState(false); - const standardRef = useRef(null); - const highRef = useRef(null); +}: MediaQualitySelectorProps): JSX.Element { + const value = useMemo(() => { + return isHighQuality ? MediaQuality.High : MediaQuality.Standard; + }, [isHighQuality]); - const handleOpenAutoFocus = useCallback( - (e: Event) => { - e.preventDefault(); - if (isHighQuality) { - highRef.current?.focus(); - } else { - standardRef.current?.focus(); - } + const handleValueChange = useCallback( + (selected: string) => { + onSelectQuality(conversationId, selected === MediaQuality.High); }, - [isHighQuality] + [conversationId, onSelectQuality] ); - const handleContentKeyDown = useCallback((ev: KeyboardEvent) => { - if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp') { - if (document.activeElement === standardRef.current) { - highRef.current?.focus(); - } else { - standardRef.current?.focus(); - } - ev.stopPropagation(); - ev.preventDefault(); - } - }, []); - return ( - - + + - - {open && ( - -
+ + + + {i18n('icu:MediaQualitySelector--title')} + + - -
- {i18n('icu:MediaQualitySelector--title')} -
- - -
-
-
- )} -
+ {i18n('icu:MediaQualitySelector--standard-quality-title')} +
+ {i18n('icu:MediaQualitySelector--standard-quality-description')} +
+ + + {i18n('icu:MediaQualitySelector--high-quality-title')} +
+ {i18n('icu:MediaQualitySelector--high-quality-description')} +
+
+ + + ); } diff --git a/ts/components/RecordingComposer.dom.tsx b/ts/components/RecordingComposer.dom.tsx index 8127aac449..7c47711f6f 100644 --- a/ts/components/RecordingComposer.dom.tsx +++ b/ts/components/RecordingComposer.dom.tsx @@ -3,7 +3,7 @@ import type { ReactNode, JSX } from 'react'; import type { LocalizerType } from '../types/I18N.std.ts'; -import { Button, ButtonSize, ButtonVariant } from './Button.dom.tsx'; +import { AxoButton } from '../axo/AxoButton.dom.tsx'; type Props = { i18n: LocalizerType; @@ -21,21 +21,16 @@ export function RecordingComposer({ return (
{children}
- - +
); } diff --git a/ts/components/conversation/AudioCapture.dom.tsx b/ts/components/conversation/AudioCapture.dom.tsx index 615c40b479..197802ca6f 100644 --- a/ts/components/conversation/AudioCapture.dom.tsx +++ b/ts/components/conversation/AudioCapture.dom.tsx @@ -11,6 +11,7 @@ import { useStartRecordingShortcut, useKeyboardShortcuts, } from '../../hooks/useKeyboardShortcuts.dom.tsx'; +import { AxoIconButton } from '../../axo/AxoIconButton.dom.tsx'; export type PropsType = { conversationId: string; @@ -50,14 +51,15 @@ export function AudioCapture({ return (
-
); diff --git a/ts/components/fun/FunButton.dom.tsx b/ts/components/fun/FunButton.dom.tsx index 0b3948226a..5ed02a95ca 100644 --- a/ts/components/fun/FunButton.dom.tsx +++ b/ts/components/fun/FunButton.dom.tsx @@ -1,11 +1,12 @@ // Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { type JSX } from 'react'; -import { VisuallyHidden } from 'react-aria'; +import { Pressable, VisuallyHidden } from 'react-aria'; import { Button } from 'react-aria-components'; import type { LocalizerType } from '../../types/I18N.std.ts'; import { FunStaticEmoji } from './FunEmoji.dom.tsx'; import { Emoji } from '../../axo/emoji.std.ts'; +import { AxoIconButton } from '../../axo/AxoIconButton.dom.tsx'; /** * Fun Picker Button @@ -18,10 +19,15 @@ export type FunPickerButtonProps = Readonly<{ export function FunPickerButton(props: FunPickerButtonProps): JSX.Element { const { i18n } = props; return ( - + + + ); }