All files / src/components/dialogs PrivacyPolicyDialog.tsx

100% Statements 25/25
100% Branches 6/6
100% Functions 6/6
100% Lines 22/22

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 981x 1x                         1x 1x   1x                     1x 9x     1x   1x 5x 5x 5x 5x   5x   5x   4x 4x   4x     5x 5x     5x                                                           30x                          
import { Close as CloseIcon } from '@mui/icons-material';
import {
  AppBar,
  Box,
  Container,
  Dialog,
  IconButton,
  Slide,
  SlideProps,
  Toolbar,
  Typography,
  useMediaQuery,
  useTheme,
} from '@mui/material';
import React from 'react';
import { useTranslation } from 'react-i18next';
 
import { meta } from '@/constants';
 
export type PrivacyPolicyDialogProps = {
  open: boolean;
  closeAction: { (): void };
};
 
export type PrivacyPolicyDialogRef = {
  scrollTop: () => void;
};
 
const Transition = React.forwardRef(function _(props: SlideProps & { children: React.ReactElement }, ref) {
  return <Slide ref={ref} direction="up" {...props} />;
});
 
const titleKeys = ['acquisition', 'purpose', 'thirdParty', 'analysis', 'disclaimer', 'copyright'];
 
export const PrivacyPolicyDialog = React.memo(function _(props: PrivacyPolicyDialogProps) {
  const dialogRef = React.useRef<HTMLDivElement>(null);
  const theme = useTheme();
  const ltSm = useMediaQuery(theme.breakpoints.down('sm'));
  const [t] = useTranslation();
 
  const handleClose = React.useCallback(() => props.closeAction && props.closeAction(), [props]);
 
  const scrollTop = React.useCallback(() => {
    // ダイアログを開く際にスクロール位置を先頭に戻す
    const element = dialogRef.current?.getElementsByClassName('MuiDialog-paperScrollPaper')[0] as Element;
    if (element) element.scrollTop = 0;
    /* istanbul ignore if */
    if (process.env.NODE_ENV === 'test') console.debug('ScrollTop Completed.');
  }, []);
 
  React.useEffect(() => {
    if (props.open) scrollTop();
  }, [props.open, scrollTop]);
 
  return (
    <Dialog
      ref={dialogRef}
      open={props.open}
      onClose={handleClose}
      TransitionComponent={Transition}
      keepMounted
      maxWidth={false}
      fullWidth={!ltSm}
      fullScreen={ltSm}
      sx={ltSm ? { maxHeight: '85vh', mt: '15vh' } : null} // モバイル端末時は Bottom Sheet 風の表示にする
      style={{ whiteSpace: 'pre-wrap' }} // 空白と改行を反映させる
    >
      <AppBar sx={{ position: 'sticky' }}>
        <Container maxWidth="xl">
          <Toolbar sx={{ justifyContent: 'space-between' }} disableGutters>
            <Typography variant="h6" data-testid="PrivacyPolicyDialog__Title">
              {t('privacyPolicy.title')}
            </Typography>
            <IconButton onClick={handleClose} data-testid="PrivacyPolicyDialog__Close">
              <CloseIcon />
            </IconButton>
          </Toolbar>
        </Container>
      </AppBar>
      <Container sx={{ pb: 10 }} data-testid="PrivacyPolicyDialog__Body">
        <Box sx={{ my: 5 }}>
          <Typography variant="subtitle1">{t('privacyPolicy.summary', { title: meta.title })}</Typography>
        </Box>
        {titleKeys.map((key) => (
          <Box key={`PrivacyPolicyDialog__Body--${key}`} sx={{ my: 5 }}>
            <Typography variant="h6" sx={{ fontWeight: 'bold' }}>
              {t('privacyPolicy.title', { context: key })}
            </Typography>
            <Typography variant="body1" sx={{ color: 'text.secondary', mt: 2 }}>
              {t('privacyPolicy.body', { context: key })}
            </Typography>
          </Box>
        ))}
      </Container>
    </Dialog>
  );
});