diff --git a/locales/en-GB/app.json b/locales/en-GB/app.json index da713a59..9366ecd0 100644 --- a/locales/en-GB/app.json +++ b/locales/en-GB/app.json @@ -4,19 +4,19 @@ }, "action": { "close": "Close", - "close_search": "Close search", "copy_link": "Copy link", "edit": "Edit", "go": "Go", "invite": "Invite", "lower_hand": "Lower hand", "no": "No", - "open_search": "Open search", "pick_reaction": "Pick reaction", "raise_hand": "Raise hand", "raise_hand_or_send_reaction": "Raise hand or send reaction", "register": "Register", "remove": "Remove", + "show_less": "Show less", + "show_more": "Show more", "sign_in": "Sign in", "sign_out": "Sign out", "submit": "Submit", @@ -62,7 +62,6 @@ "preferences": "Preferences", "profile": "Profile", "reaction": "Reaction", - "search": "Search", "settings": "Settings", "something_went_wrong": "Something went wrong", "unencrypted": "Not encrypted", @@ -128,7 +127,6 @@ "rageshake_sending": "Sending…", "rageshake_sending_logs": "Sending debug logs…", "rageshake_sent": "Thanks!", - "reaction_search": "Search reactions…", "recaptcha_caption": "This site is protected by ReCAPTCHA and the Google <2>Privacy Policy and <6>Terms of Service apply.<9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)", "recaptcha_dismissed": "Recaptcha dismissed", "recaptcha_not_loaded": "Recaptcha not loaded", diff --git a/src/Modal.module.css b/src/Modal.module.css index fae3a6fb..fc4cf366 100644 --- a/src/Modal.module.css +++ b/src/Modal.module.css @@ -6,6 +6,7 @@ Please see LICENSE in the repository root for full details. */ .modal { + --inset-inline: 520px; display: flex; flex-direction: column; } @@ -35,7 +36,7 @@ Please see LICENSE in the repository root for full details. .drawer { background: var(--cpd-color-bg-canvas-default); inset-block-end: 0; - inset-inline: max(0px, calc((100% - 520px) / 2)); + inset-inline: max(0px, calc((100% - var(--inset-inline)) / 2)); max-block-size: 90%; border-start-start-radius: var(--border-radius); border-start-end-radius: var(--border-radius); diff --git a/src/Overlay.module.css b/src/Overlay.module.css index 7f24e7f1..6253bcb0 100644 --- a/src/Overlay.module.css +++ b/src/Overlay.module.css @@ -42,8 +42,9 @@ Please see LICENSE in the repository root for full details. } .overlay.animate { + --overlay-top: 50%; left: 50%; - top: 50%; + top: var(--overlay-top); transform: translate(-50%, -50%); } diff --git a/src/button/ReactionToggleButton.module.css b/src/button/ReactionToggleButton.module.css index 7c97c2de..90c6af02 100644 --- a/src/button/ReactionToggleButton.module.css +++ b/src/button/ReactionToggleButton.module.css @@ -3,78 +3,99 @@ } .reactionPopupMenu { + --reaction-button-padding: 10px; + --reaction-button-fontsize: 20px; + --reaction-button-gap: var(--cpd-separator-spacing); display: flex; + width: fit-content; } -.reactionPopupMenuModal { - width: fit-content !important; - top: 82vh !important; +@media (max-width: 420px) { + .reactionPopupMenu { + --reaction-button-padding: 8px; + --reaction-button-fontsize: 16px; + --reaction-button-gap: 6px; + } } -.reactionPopupMenuModal > div > div { - padding-inline: var(--cpd-space-6x) !important; - padding-block: var(--cpd-space-6x) var(--cpd-space-8x) !important; +div.reactionPopupMenuRoot.reactionPopupMenuModal { + --overlay-top: 82vh; + width: fit-content; } -.reactionPopupMenu menu { - margin: 0; - padding: 0; - display: flex; - flex-wrap: wrap; - gap: var(--cpd-separator-spacing); +div.reactionPopupMenuRoot { + /* Center the drawer */ + --inset-inline: 30em; +} + +.reactionPopupMenuRoot > div { + width: fit-content; + max-width: 100vw; +} + +div.reactionPopupMenuRoot.reactionPopupMenuModal > div > div { + padding-inline: var(--cpd-space-6x); + padding-block: var(--cpd-space-6x); } .reactionPopupMenu section { height: fit-content; - margin-top: auto; - margin-bottom: auto; + flex: 1; + max-width: fit-content; } -.reactionPopupMenuItem { - list-style: none; +.reactionPopupMenu section.reactionsMenuSection { + margin: auto 0; + flex: auto; } .reactionsMenu { - min-height: 3em; + margin: 0; + padding: 0; + flex-grow: 1; + gap: var(--reaction-button-gap); + /* Height of 3 rows plus padding. */ + max-height: calc( + ((var(--reaction-button-fontsize) + var(--cpd-separator-spacing)) * 2) * 3 + ); + max-width: calc( + ((var(--reaction-button-fontsize) + var(--cpd-separator-spacing)) * 2) * 5 + ); + overflow-x: hidden; + overflow-y: auto; + list-style: none; + flex-wrap: wrap; + display: flex; + flex-wrap: wrap; + flex-direction: row; + justify-content: start; + align-items: auto; + align-content: start; + width: fit-content; +} + +.reactionsMenu > * { + flex: 0 0 auto; } .reactionButton { - padding: 1em; - font-size: 1.6em; - width: 1.4em; - height: 1.4em; + padding: var(--reaction-button-padding); border-radius: var(--cpd-radius-pill-effect); -} - -@media (max-width: 800px) { - .reactionButton { - padding: 1em; - font-size: 1em; - width: 1em; - height: 1em; - min-block-size: unset; - } + font-size: var(--reaction-button-fontsize); + min-block-size: unset; + border: none; + aspect-ratio: 1 / 1; + height: 100%; } .verticalSeperator { background-color: var(--cpd-color-gray-800); - width: 1px; + width: 2px; height: auto; margin-left: var(--cpd-separator-spacing); margin-right: var(--cpd-separator-spacing); } -.searchForm { - display: flex; - flex-direction: row; - gap: var(--cpd-separator-spacing); - margin-bottom: var(--cpd-space-3x); -} - -.searchForm > label { - flex: auto; -} - .alert { margin-bottom: var(--cpd-space-3x); animation: grow-in 200ms; diff --git a/src/button/ReactionToggleButton.test.tsx b/src/button/ReactionToggleButton.test.tsx index b13b74fa..14218820 100644 --- a/src/button/ReactionToggleButton.test.tsx +++ b/src/button/ReactionToggleButton.test.tsx @@ -5,8 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ -import { fireEvent, render } from "@testing-library/react"; -import { act } from "react"; +import { render } from "@testing-library/react"; import { expect, test } from "vitest"; import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc"; import { TooltipProvider } from "@vector-im/compound-web"; @@ -122,7 +121,7 @@ test("Can react with emoji", async () => { ]); }); -test("Can search for and send emoji", async () => { +test("Can fully expand emoji picker", async () => { const user = userEvent.setup(); const room = new MockRoom(memberUserIdAlice); const rtcSession = new MockRTCSession(room, membership); @@ -130,9 +129,7 @@ test("Can search for and send emoji", async () => { , ); await user.click(getByLabelText("action.raise_hand_or_send_reaction")); - await user.click(getByLabelText("action.open_search")); - // Search should autofocus. - await user.keyboard("crickets"); + await user.click(getByLabelText("action.show_more")); expect(container).toMatchSnapshot(); await user.click(getByText("🦗")); @@ -152,38 +149,6 @@ test("Can search for and send emoji", async () => { ]); }); -test("Can search for and send emoji with the keyboard", async () => { - const user = userEvent.setup(); - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); - const { getByLabelText, getByPlaceholderText, container } = render( - , - ); - await user.click(getByLabelText("action.raise_hand_or_send_reaction")); - await user.click(getByLabelText("action.open_search")); - const searchField = getByPlaceholderText("reaction_search"); - // Search should autofocus. - await user.keyboard("crickets"); - expect(container).toMatchSnapshot(); - act(() => { - fireEvent.keyDown(searchField, { key: "Enter" }); - }); - expect(room.testSentEvents).toEqual([ - [ - undefined, - ElementCallReactionEventType, - { - "m.relates_to": { - event_id: memberEventAlice, - rel_type: "m.reference", - }, - name: "crickets", - emoji: "🦗", - }, - ], - ]); -}); - test("Can close search", async () => { const user = userEvent.setup(); const room = new MockRoom(memberUserIdAlice); @@ -192,23 +157,7 @@ test("Can close search", async () => { , ); await user.click(getByLabelText("action.raise_hand_or_send_reaction")); - await user.click(getByLabelText("action.open_search")); - await user.click(getByLabelText("action.close_search")); - expect(container).toMatchSnapshot(); -}); - -test("Can close search with the escape key", async () => { - const user = userEvent.setup(); - const room = new MockRoom(memberUserIdAlice); - const rtcSession = new MockRTCSession(room, membership); - const { getByLabelText, container, getByPlaceholderText } = render( - , - ); - await user.click(getByLabelText("action.raise_hand_or_send_reaction")); - await user.click(getByLabelText("action.open_search")); - const searchField = getByPlaceholderText("reaction_search"); - act(() => { - fireEvent.keyDown(searchField, { key: "Escape" }); - }); + await user.click(getByLabelText("action.show_more")); + await user.click(getByLabelText("action.show_less")); expect(container).toMatchSnapshot(); }); diff --git a/src/button/ReactionToggleButton.tsx b/src/button/ReactionToggleButton.tsx index 984d2f4c..1c049497 100644 --- a/src/button/ReactionToggleButton.tsx +++ b/src/button/ReactionToggleButton.tsx @@ -5,24 +5,16 @@ SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ +import { Button as CpdButton, Tooltip, Alert } from "@vector-im/compound-web"; import { - Button as CpdButton, - Tooltip, - Search, - Form, - Alert, -} from "@vector-im/compound-web"; -import { - SearchIcon, - CloseIcon, RaisedHandSolidIcon, ReactionIcon, + ChevronDownIcon, + ChevronUpIcon, } from "@vector-im/compound-design-tokens/assets/web/icons"; import { - ChangeEventHandler, ComponentPropsWithoutRef, FC, - KeyboardEventHandler, ReactNode, useCallback, useEffect, @@ -84,43 +76,11 @@ export function ReactionPopupMenu({ canReact: boolean; }): ReactNode { const { t } = useTranslation(); - const [searchText, setSearchText] = useState(""); - const [isSearching, setIsSearching] = useState(false); - const onSearch = useCallback>((ev) => { - ev.preventDefault(); - setSearchText(ev.target.value.trim().toLocaleLowerCase()); - }, []); + const [isFullyExpanded, setExpanded] = useState(false); const filteredReactionSet = useMemo( - () => - ReactionSet.filter( - (reaction) => - !isSearching || - (!!searchText && - (reaction.name.startsWith(searchText) || - reaction.alias?.some((a) => a.startsWith(searchText)))), - ).slice(0, 6), - [searchText, isSearching], - ); - - const onSearchKeyDown = useCallback>( - (ev) => { - if (ev.key === "Enter") { - ev.preventDefault(); - if (!canReact) { - return; - } - if (filteredReactionSet.length !== 1) { - return; - } - sendReaction(filteredReactionSet[0]); - setIsSearching(false); - } else if (ev.key === "Escape") { - ev.preventDefault(); - setIsSearching(false); - } - }, - [sendReaction, filteredReactionSet, canReact, setIsSearching], + () => (isFullyExpanded ? ReactionSet : ReactionSet.slice(0, 5)), + [isFullyExpanded], ); const label = isHandRaised ? t("action.lower_hand") : t("action.raise_hand"); return ( @@ -148,35 +108,15 @@ export function ReactionPopupMenu({
-
- {isSearching ? ( - <> - - - setIsSearching(false)} - /> - - - ) : null} - +
+ {filteredReactionSet.map((reaction) => ( -
  • +
  • - {!isSearching ? ( -
    -
  • - - setIsSearching(true)} - /> - -
  • -
    - ) : null} +
    + + setExpanded(!isFullyExpanded)} + /> + +
    ); @@ -335,12 +277,13 @@ export function ReactionToggleButton({ title={t("action.pick_reaction")} hideHeader classNameModal={styles.reactionPopupMenuModal} + className={styles.reactionPopupMenuRoot} onDismiss={() => setShowReactionsMenu(false)} > void sendRelation(reaction)} toggleRaisedHand={toggleRaisedHand} /> diff --git a/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap b/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap index bee0bdb1..604a615a 100644 --- a/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap +++ b/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap @@ -10,7 +10,7 @@ exports[`Can close search 1`] = ` aria-expanded="true" aria-haspopup="true" aria-label="action.raise_hand_or_send_reaction" - aria-labelledby=":rec:" + aria-labelledby=":r9l:" class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59" data-kind="primary" data-size="lg" @@ -36,16 +36,19 @@ exports[`Can close search 1`] = ` `; -exports[`Can close search with the escape key 1`] = ` -
    +exports[`Can fully expand emoji picker 1`] = ` + `; - -exports[`Can search for and send emoji 1`] = ` - -`; - -exports[`Can search for and send emoji with the keyboard 1`] = ` - -`;