Skip to content

Commit

Permalink
Merge pull request #727 from woowacourse-teams/refactor/722-tag-regis…
Browse files Browse the repository at this point in the history
…tration-announcement

[접근성개선] 스크린 리더가 태그 등록 메시지 안내
  • Loading branch information
Jaymyong66 authored Oct 8, 2024
2 parents b770476 + 0bcc07b commit 010413d
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 6 deletions.
21 changes: 21 additions & 0 deletions frontend/src/components/ScreenReaderOnly/ScreenReaderOnly.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const ScreenReaderOnly = () => (
<div
id='screen-reader'
aria-live='polite'
aria-hidden='true'
style={{
position: 'absolute',
width: '1px',
height: '1px',
margin: '-1px',
padding: '0',
border: '0',
overflow: 'hidden',
clip: 'rect(0, 0, 0, 0)',
whiteSpace: 'nowrap',
wordWrap: 'normal',
}}
/>
);

export default ScreenReaderOnly;
10 changes: 4 additions & 6 deletions frontend/src/components/TagInput/TagInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ChangeEvent, Dispatch, KeyboardEvent, SetStateAction } from 'react';

import { Flex, Input, TagButton, Text } from '@/components';
import { ToastContext } from '@/contexts';
import { useCustomContext } from '@/hooks';
import { useCustomContext, useScreenReader } from '@/hooks';
import { validateTagLength } from '@/service/validates';
import { theme } from '@/style/theme';

Expand All @@ -16,6 +16,7 @@ interface Props {

const TagInput = ({ value, handleValue, resetValue, tags, setTags }: Props) => {
const { failAlert } = useCustomContext(ToastContext);
const { updateScreenReaderMessage } = useScreenReader();

const handleSpaceBarAndEnterKeydown = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === ' ' || e.key === 'Enter') {
Expand All @@ -26,15 +27,12 @@ const TagInput = ({ value, handleValue, resetValue, tags, setTags }: Props) => {
};

const addTag = () => {
if (value === '') {
return;
}

if (tags.includes(value)) {
if (value === '' || tags.includes(value)) {
return;
}

setTags((prev) => [...prev, value]);
updateScreenReaderMessage(`${value} 태그 등록`);
};

const handleTagInput = (e: ChangeEvent<HTMLInputElement>) => {
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ export { default as NoSearchResults } from './NoSearchResults/NoSearchResults';
// Skeleton UI
export { default as LoadingBall } from './LoadingBall/LoadingBall';
export { default as LoadingFallback } from './LoadingFallback/LoadingFallback';

// ScreenReader
export { default as ScreenReaderOnly } from './ScreenReaderOnly/ScreenReaderOnly';
1 change: 1 addition & 0 deletions frontend/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { useDropdown } from './useDropdown';
export { useHeaderHeight } from './useHeaderHeight';
export { useInput } from './useInput';
export { useInputWithValidate } from './useInputWithValidate';
export { useScreenReader } from './useScreenReader';
export { useScrollToTargetElement } from './useScrollToTargetElement';
export { useWindowWidth } from './useWindowWidth';
export { useToggle } from './useToggle';
Expand Down
19 changes: 19 additions & 0 deletions frontend/src/hooks/useScreenReader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useCallback } from 'react';

export const useScreenReader = () => {
const updateScreenReaderMessage = useCallback((message: string) => {
const element = document.getElementById('screen-reader');

if (element) {
element.setAttribute('aria-hidden', 'false');
element.textContent = message;

setTimeout(() => {
element.setAttribute('aria-hidden', 'true');
element.textContent = '';
});
}
}, []);

return { updateScreenReaderMessage };
};
2 changes: 2 additions & 0 deletions frontend/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { RouterProvider } from 'react-router-dom';

import { AuthProvider, HeaderProvider, ToastProvider } from '@/contexts';

import { ScreenReaderOnly } from './components/index';
import router from './routes/router';
import GlobalStyles from './style/GlobalStyles';
import { theme } from './style/theme';
Expand Down Expand Up @@ -49,6 +50,7 @@ enableMocking().then(() => {
<HeaderProvider>
<GlobalStyles />
<RouterProvider router={router} />
<ScreenReaderOnly />
</HeaderProvider>
</ToastProvider>
</AuthProvider>
Expand Down

0 comments on commit 010413d

Please sign in to comment.