Skip to content

Commit 8f48b52

Browse files
authored
fix: trap focus in opened modal (#2278)
### 🎯 Goal When a modal dialog is displayed in chat, it's still possible to `Tab` your way through the focusable elements outside of the modal. The common practice is to "trap" the focus inside the modal, so that `Tab` cycles through the focusable elements inside the modal only. ### 🛠 Implementation details Implementing focus trap properly is not as easy as it seems - so it's better to use a proven solution. This PR uses `FocusScope` from React Aria. ### 🎨 UI Changes https://github.com/GetStream/stream-chat-react/assets/975978/a6ac10f0-b4a9-44b2-9beb-2a822ceb0ab2
1 parent 706cf3d commit 8f48b52

File tree

4 files changed

+170
-43
lines changed

4 files changed

+170
-43
lines changed

Diff for: examples/typescript/yarn.lock

+58
Original file line numberDiff line numberDiff line change
@@ -1917,6 +1917,57 @@
19171917
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45"
19181918
integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==
19191919

1920+
"@react-aria/focus@^3.16.1":
1921+
version "3.16.1"
1922+
resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.16.1.tgz#557a451cbe901153d23045ce27851b05709db24a"
1923+
integrity sha512-3ZEYc+hWqDQX7fA54ZOTkED8OGXs9+K9fYmjD1IdjZJAJS/2/AJ95PgIQ29zBkl9D9TAi4Nb3tJ/3+H/02UzoA==
1924+
dependencies:
1925+
"@react-aria/interactions" "^3.21.0"
1926+
"@react-aria/utils" "^3.23.1"
1927+
"@react-types/shared" "^3.22.0"
1928+
"@swc/helpers" "^0.5.0"
1929+
clsx "^2.0.0"
1930+
1931+
"@react-aria/interactions@^3.21.0":
1932+
version "3.21.0"
1933+
resolved "https://registry.yarnpkg.com/@react-aria/interactions/-/interactions-3.21.0.tgz#c04f4eb59ae70b723d7be8d5f8eb4e2802087a49"
1934+
integrity sha512-sPuzEl4Xq/BR5gbYr2R/sDzwlX9NdJ02i8Ew2rEy2hLMlf1jAeUAdTg/G+K9baWJ8acV9fZv6h/mdV3dXGLPSg==
1935+
dependencies:
1936+
"@react-aria/ssr" "^3.9.1"
1937+
"@react-aria/utils" "^3.23.1"
1938+
"@react-types/shared" "^3.22.0"
1939+
"@swc/helpers" "^0.5.0"
1940+
1941+
"@react-aria/ssr@^3.9.1":
1942+
version "3.9.1"
1943+
resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.9.1.tgz#a1252fd5ef87eada810dd9dd6751a5e21359d1d2"
1944+
integrity sha512-NqzkLFP8ZVI4GSorS0AYljC13QW2sc8bDqJOkBvkAt3M8gbcAXJWVRGtZBCRscki9RZF+rNlnPdg0G0jYkhJcg==
1945+
dependencies:
1946+
"@swc/helpers" "^0.5.0"
1947+
1948+
"@react-aria/utils@^3.23.1":
1949+
version "3.23.1"
1950+
resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.23.1.tgz#a082a5ffb97a7b9c03d522dcedfc251af8473e44"
1951+
integrity sha512-iXibf9ojqdoygbvy/++v5cKLKgjc/5ZmKV8/9u/2Hkpha1cf5Td/Z+Vl42B6giUBAsuDio5kuZYfYC7Uk+t8ag==
1952+
dependencies:
1953+
"@react-aria/ssr" "^3.9.1"
1954+
"@react-stately/utils" "^3.9.0"
1955+
"@react-types/shared" "^3.22.0"
1956+
"@swc/helpers" "^0.5.0"
1957+
clsx "^2.0.0"
1958+
1959+
"@react-stately/utils@^3.9.0":
1960+
version "3.9.0"
1961+
resolved "https://registry.yarnpkg.com/@react-stately/utils/-/utils-3.9.0.tgz#9cb2c8eea5dd1b58256ecb436b963c01526bae37"
1962+
integrity sha512-yPKFY1F88HxuZ15BG2qwAYxtpE4HnIU0Ofi4CuBE0xC6I8mwo4OQjDzi+DZjxQngM9D6AeTTD6F1V8gkozA0Gw==
1963+
dependencies:
1964+
"@swc/helpers" "^0.5.0"
1965+
1966+
"@react-types/shared@^3.22.0":
1967+
version "3.22.0"
1968+
resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.22.0.tgz#70f85aad46cd225f7fcb29f1c2b5213163605074"
1969+
integrity sha512-yVOekZWbtSmmiThGEIARbBpnmUIuePFlLyctjvCbgJgGhz8JnEJOipLQ/a4anaWfzAgzSceQP8j/K+VOOePleA==
1970+
19201971
"@rgrove/parse-xml@^3.0.0":
19211972
version "3.0.0"
19221973
resolved "https://registry.yarnpkg.com/@rgrove/parse-xml/-/parse-xml-3.0.0.tgz#29d45eadeb6c9a701038cfb9fab2356a7bdc71d5"
@@ -2118,6 +2169,13 @@
21182169
"@svgr/plugin-svgo" "^5.5.0"
21192170
loader-utils "^2.0.0"
21202171

2172+
"@swc/helpers@^0.5.0":
2173+
version "0.5.6"
2174+
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.6.tgz#d16d8566b7aea2bef90d059757e2d77f48224160"
2175+
integrity sha512-aYX01Ke9hunpoCexYAgQucEpARGQ5w/cqHFrIR+e9gdKb1QWTsVJuTJ2ozQzIAxLyRQe/m+2RqzkyOOGiMKRQA==
2176+
dependencies:
2177+
tslib "^2.4.0"
2178+
21212179
"@testing-library/dom@*":
21222180
version "7.31.2"
21232181
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.31.2.tgz#df361db38f5212b88555068ab8119f5d841a8c4a"

Diff for: package.json

+5-4
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"@babel/runtime": "^7.23.6",
6363
"@braintree/sanitize-url": "^6.0.4",
6464
"@popperjs/core": "^2.11.5",
65+
"@react-aria/focus": "^3.16.1",
6566
"clsx": "^2.0.0",
6667
"dayjs": "^1.10.4",
6768
"emoji-regex": "^9.2.0",
@@ -157,10 +158,10 @@
157158
"@types/lodash.throttle": "^4.1.7",
158159
"@types/lodash.uniqby": "^4.7.7",
159160
"@types/moment": "^2.13.0",
160-
"@types/react": "^18.0.8",
161-
"@types/react-dom": "^18.0.3",
162-
"@types/react-image-gallery": "^1.0.5",
163-
"@types/react-is": "^17.0.3",
161+
"@types/react": "^18.2.55",
162+
"@types/react-dom": "^18.2.19",
163+
"@types/react-image-gallery": "^1.2.4",
164+
"@types/react-is": "^18.2.4",
164165
"@types/textarea-caret": "3.0.0",
165166
"@types/uuid": "^8.3.0",
166167
"@typescript-eslint/eslint-plugin": "4.27.0",

Diff for: src/components/Modal/Modal.tsx

+20-17
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { PropsWithChildren, useEffect, useRef } from 'react';
2+
import { FocusScope } from '@react-aria/focus';
23

34
import { CloseIconRound } from './icons';
45

@@ -42,24 +43,26 @@ export const Modal = ({ children, onClose, open }: PropsWithChildren<ModalProps>
4243

4344
return (
4445
<div className='str-chat__modal str-chat__modal--open' onClick={handleClick}>
45-
<button className='str-chat__modal__close-button' ref={closeRef} title={t<string>('Close')}>
46-
{themeVersion === '2' && <CloseIconRound />}
46+
<FocusScope autoFocus contain>
47+
<button className='str-chat__modal__close-button' ref={closeRef} title={t<string>('Close')}>
48+
{themeVersion === '2' && <CloseIconRound />}
4749

48-
{themeVersion === '1' && (
49-
<>
50-
{t<string>('Close')}
51-
<svg height='10' width='10' xmlns='http://www.w3.org/2000/svg'>
52-
<path
53-
d='M9.916 1.027L8.973.084 5 4.058 1.027.084l-.943.943L4.058 5 .084 8.973l.943.943L5 5.942l3.973 3.974.943-.943L5.942 5z'
54-
fillRule='evenodd'
55-
/>
56-
</svg>
57-
</>
58-
)}
59-
</button>
60-
<div className='str-chat__modal__inner str-chat-react__modal__inner' ref={innerRef}>
61-
{children}
62-
</div>
50+
{themeVersion === '1' && (
51+
<>
52+
{t<string>('Close')}
53+
<svg height='10' width='10' xmlns='http://www.w3.org/2000/svg'>
54+
<path
55+
d='M9.916 1.027L8.973.084 5 4.058 1.027.084l-.943.943L4.058 5 .084 8.973l.943.943L5 5.942l3.973 3.974.943-.943L5.942 5z'
56+
fillRule='evenodd'
57+
/>
58+
</svg>
59+
</>
60+
)}
61+
</button>
62+
<div className='str-chat__modal__inner str-chat-react__modal__inner' ref={innerRef}>
63+
{children}
64+
</div>
65+
</FocusScope>
6366
</div>
6467
);
6568
};

Diff for: yarn.lock

+87-22
Original file line numberDiff line numberDiff line change
@@ -1955,6 +1955,57 @@
19551955
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.5.tgz#db5a11bf66bdab39569719555b0f76e138d7bd64"
19561956
integrity sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==
19571957

1958+
"@react-aria/focus@^3.16.1":
1959+
version "3.16.1"
1960+
resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.16.1.tgz#557a451cbe901153d23045ce27851b05709db24a"
1961+
integrity sha512-3ZEYc+hWqDQX7fA54ZOTkED8OGXs9+K9fYmjD1IdjZJAJS/2/AJ95PgIQ29zBkl9D9TAi4Nb3tJ/3+H/02UzoA==
1962+
dependencies:
1963+
"@react-aria/interactions" "^3.21.0"
1964+
"@react-aria/utils" "^3.23.1"
1965+
"@react-types/shared" "^3.22.0"
1966+
"@swc/helpers" "^0.5.0"
1967+
clsx "^2.0.0"
1968+
1969+
"@react-aria/interactions@^3.21.0":
1970+
version "3.21.0"
1971+
resolved "https://registry.yarnpkg.com/@react-aria/interactions/-/interactions-3.21.0.tgz#c04f4eb59ae70b723d7be8d5f8eb4e2802087a49"
1972+
integrity sha512-sPuzEl4Xq/BR5gbYr2R/sDzwlX9NdJ02i8Ew2rEy2hLMlf1jAeUAdTg/G+K9baWJ8acV9fZv6h/mdV3dXGLPSg==
1973+
dependencies:
1974+
"@react-aria/ssr" "^3.9.1"
1975+
"@react-aria/utils" "^3.23.1"
1976+
"@react-types/shared" "^3.22.0"
1977+
"@swc/helpers" "^0.5.0"
1978+
1979+
"@react-aria/ssr@^3.9.1":
1980+
version "3.9.1"
1981+
resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.9.1.tgz#a1252fd5ef87eada810dd9dd6751a5e21359d1d2"
1982+
integrity sha512-NqzkLFP8ZVI4GSorS0AYljC13QW2sc8bDqJOkBvkAt3M8gbcAXJWVRGtZBCRscki9RZF+rNlnPdg0G0jYkhJcg==
1983+
dependencies:
1984+
"@swc/helpers" "^0.5.0"
1985+
1986+
"@react-aria/utils@^3.23.1":
1987+
version "3.23.1"
1988+
resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.23.1.tgz#a082a5ffb97a7b9c03d522dcedfc251af8473e44"
1989+
integrity sha512-iXibf9ojqdoygbvy/++v5cKLKgjc/5ZmKV8/9u/2Hkpha1cf5Td/Z+Vl42B6giUBAsuDio5kuZYfYC7Uk+t8ag==
1990+
dependencies:
1991+
"@react-aria/ssr" "^3.9.1"
1992+
"@react-stately/utils" "^3.9.0"
1993+
"@react-types/shared" "^3.22.0"
1994+
"@swc/helpers" "^0.5.0"
1995+
clsx "^2.0.0"
1996+
1997+
"@react-stately/utils@^3.9.0":
1998+
version "3.9.0"
1999+
resolved "https://registry.yarnpkg.com/@react-stately/utils/-/utils-3.9.0.tgz#9cb2c8eea5dd1b58256ecb436b963c01526bae37"
2000+
integrity sha512-yPKFY1F88HxuZ15BG2qwAYxtpE4HnIU0Ofi4CuBE0xC6I8mwo4OQjDzi+DZjxQngM9D6AeTTD6F1V8gkozA0Gw==
2001+
dependencies:
2002+
"@swc/helpers" "^0.5.0"
2003+
2004+
"@react-types/shared@^3.22.0":
2005+
version "3.22.0"
2006+
resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.22.0.tgz#70f85aad46cd225f7fcb29f1c2b5213163605074"
2007+
integrity sha512-yVOekZWbtSmmiThGEIARbBpnmUIuePFlLyctjvCbgJgGhz8JnEJOipLQ/a4anaWfzAgzSceQP8j/K+VOOePleA==
2008+
19582009
"@rgrove/parse-xml@^3.0.0":
19592010
version "3.0.0"
19602011
resolved "https://registry.yarnpkg.com/@rgrove/parse-xml/-/parse-xml-3.0.0.tgz#29d45eadeb6c9a701038cfb9fab2356a7bdc71d5"
@@ -2199,6 +2250,13 @@
21992250
"@stream-io/escape-string-regexp" "^5.0.1"
22002251
lodash.deburr "^4.1.0"
22012252

2253+
"@swc/helpers@^0.5.0":
2254+
version "0.5.6"
2255+
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.6.tgz#d16d8566b7aea2bef90d059757e2d77f48224160"
2256+
integrity sha512-aYX01Ke9hunpoCexYAgQucEpARGQ5w/cqHFrIR+e9gdKb1QWTsVJuTJ2ozQzIAxLyRQe/m+2RqzkyOOGiMKRQA==
2257+
dependencies:
2258+
tslib "^2.4.0"
2259+
22022260
"@testing-library/dom@^7.28.1":
22032261
version "7.30.0"
22042262
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.30.0.tgz#53697851f7708a1448cc30b74a2ea056dd709cd6"
@@ -2527,35 +2585,42 @@
25272585
integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==
25282586

25292587
"@types/prop-types@*":
2530-
version "15.7.5"
2531-
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
2532-
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
2588+
version "15.7.11"
2589+
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563"
2590+
integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==
25332591

2534-
"@types/react-dom@^18.0.0", "@types/react-dom@^18.0.3":
2592+
"@types/react-dom@^18.0.0":
25352593
version "18.0.5"
25362594
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.5.tgz#330b2d472c22f796e5531446939eacef8378444a"
25372595
integrity sha512-OWPWTUrY/NIrjsAPkAk1wW9LZeIjSvkXRhclsFO8CZcZGCOg2G0YZy4ft+rOyYxy8B7ui5iZzi9OkDebZ7/QSA==
25382596
dependencies:
25392597
"@types/react" "*"
25402598

2541-
"@types/react-image-gallery@^1.0.5":
2542-
version "1.0.5"
2543-
resolved "https://registry.yarnpkg.com/@types/react-image-gallery/-/react-image-gallery-1.0.5.tgz#563234ac0fa131e2920199d191fca0c1594ba29d"
2544-
integrity sha512-CjIjFXmV7zGRO/FkdtPK31Rwj0ud7Xz/9e5o4DCX2Lx96jR8+0xCmKeUAQQVN3pKc6jUjOC0f/aEylhKg8vhAw==
2599+
"@types/react-dom@^18.2.19":
2600+
version "18.2.19"
2601+
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.19.tgz#b84b7c30c635a6c26c6a6dfbb599b2da9788be58"
2602+
integrity sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==
25452603
dependencies:
25462604
"@types/react" "*"
25472605

2548-
"@types/react-is@^17.0.3":
2549-
version "17.0.3"
2550-
resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-17.0.3.tgz#2d855ba575f2fc8d17ef9861f084acc4b90a137a"
2551-
integrity sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==
2606+
"@types/react-image-gallery@^1.2.4":
2607+
version "1.2.4"
2608+
resolved "https://registry.yarnpkg.com/@types/react-image-gallery/-/react-image-gallery-1.2.4.tgz#17c2e3416a5c9ecab14588eac593d4a7aa583163"
2609+
integrity sha512-H0xpmT5rlSH0qiTvcUDCPDLRBi3J3Xa4COqaDqGb7ffLFpQoPAxpZdNuKCuThhFI0xJmNnMubZiD6B3kCBHtcw==
25522610
dependencies:
25532611
"@types/react" "*"
25542612

2555-
"@types/react@*", "@types/react@^18.0.8":
2556-
version "18.0.14"
2557-
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.14.tgz#e016616ffff51dba01b04945610fe3671fdbe06d"
2558-
integrity sha512-x4gGuASSiWmo0xjDLpm5mPb52syZHJx02VKbqUKdLmKtAwIh63XClGsiTI1K6DO5q7ox4xAsQrU+Gl3+gGXF9Q==
2613+
"@types/react-is@^18.2.4":
2614+
version "18.2.4"
2615+
resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-18.2.4.tgz#95a92829de452662348ce08349ca65623c50daf7"
2616+
integrity sha512-wBc7HgmbCcrvw0fZjxbgz/xrrlZKzEqmABBMeSvpTvdm25u6KI6xdIi9pRE2G0C1Lw5ETFdcn4UbYZ4/rpqUYw==
2617+
dependencies:
2618+
"@types/react" "*"
2619+
2620+
"@types/react@*", "@types/react@^18.2.55":
2621+
version "18.2.55"
2622+
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.55.tgz#38141821b7084404b5013742bc4ae08e44da7a67"
2623+
integrity sha512-Y2Tz5P4yz23brwm2d7jNon39qoAtMMmalOQv6+fEFt1mT+FcM3D841wDpoUvFXhaYenuROCy3FZYqdTjM7qVyA==
25592624
dependencies:
25602625
"@types/prop-types" "*"
25612626
"@types/scheduler" "*"
@@ -2572,9 +2637,9 @@
25722637
integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==
25732638

25742639
"@types/scheduler@*":
2575-
version "0.16.2"
2576-
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
2577-
integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
2640+
version "0.16.8"
2641+
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.8.tgz#ce5ace04cfeabe7ef87c0091e50752e36707deff"
2642+
integrity sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==
25782643

25792644
"@types/semver@^7.3.12":
25802645
version "7.3.13"
@@ -5009,9 +5074,9 @@ cssstyle@^2.3.0:
50095074
cssom "~0.3.6"
50105075

50115076
csstype@^3.0.2:
5012-
version "3.1.0"
5013-
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2"
5014-
integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==
5077+
version "3.1.3"
5078+
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
5079+
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
50155080

50165081
cyclist@^1.0.1:
50175082
version "1.0.1"

0 commit comments

Comments
 (0)