diff --git a/src/js/components/Ballot/OfficeInfoModal.jsx b/src/js/components/Ballot/OfficeInfoModal.jsx new file mode 100644 index 000000000..4feea31b5 --- /dev/null +++ b/src/js/components/Ballot/OfficeInfoModal.jsx @@ -0,0 +1,192 @@ +/* eslint-disable react/jsx-one-expression-per-line */ +/* eslint-disable react/no-unescaped-entities */ +/* eslint-disable react/jsx-closing-tag-location */ +import React from 'react'; +import PropTypes from 'prop-types'; +import styled, { createGlobalStyle } from 'styled-components'; +import DesignTokenColors from '../../common/components/Style/DesignTokenColors'; +import ModalDisplayTemplateA from '../Widgets/ModalDisplayTemplateA'; + +const OfficeInfoModal = ({ isOpen, onClose, officeName }) => { + const dialogTitleJSX = ( + + + About the office of <strong>{officeName}</strong> + + + ); + + const textFieldJSX = ( +
+ + + + + +

Watch video
(25 seconds)

+
+
+ + +

+ An {officeName} is the chief legal officer of a state, + responsible for enforcing laws, protecting citizens' rights, and representing + the public interest in legal matters. +

+

+ They oversee investigations, defend state laws in court, and provide legal + guidance to government agencies. +

+ {/* eslint-disable react/jsx-indent */} +

+ Because they influence everything from consumer protections to civil rights + and election integrity, choosing an {officeName} in an election is + crucial—it determines who will uphold justice, safeguard democratic + processes, and hold powerful entities accountable on behalf of the public. +

+ {/* eslint-enable react/jsx-indent */} +
+
+
+ + + + Close + + +
+ ); + + return ( + <> + + + + + ); +}; + +OfficeInfoModal.propTypes = { + isOpen: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + officeName: PropTypes.string.isRequired, +}; + +// Global Styles +const WidenPreviewModal = createGlobalStyle` + .MuiDialog-paper:has(#closeModalDisplayTemplateAofficeInfoModal) { + max-width: 860px !important; + width: 96% !important; + } +`; + +const SoftenCorners = createGlobalStyle` + .MuiDialog-paper:has(#closeModalDisplayTemplateAofficeInfoModal) { + border-radius: 14px !important; + } +`; + +// Styles +const HeaderRow = styled.div` + padding: 0px 12px 0 18px; +`; + +const Title = styled.h3` + font-size: 28px; + font-weight: 400; +`; + +const ModalBody = styled.div` + background: ${DesignTokenColors.whiteUI}; + border: 1px solid ${DesignTokenColors.neutralUI200}; + border-radius: 10px; + padding: 14px; +`; + +// FLEXBOX LAYOUT +const FlexContainer = styled.div` + display: flex; + gap: 20px; + align-items: flex-start; + + /* Stack vertically on mobile */ + @media (max-width: 768px) { + flex-direction: column; + } +`; + +const LeftColumn = styled.div` + flex: 0 0 180px; + + @media (max-width: 768px) { + flex: 1; + width: 100%; + } +`; + +const RightColumn = styled.div` + flex: 1; + + p { + margin-bottom: 12px; + line-height: 1.6; + } +`; + +// Video placeholder +const VideoPlaceholder = styled.div` + width: 100%; + aspect-ratio: 4/3; + background: ${DesignTokenColors.neutral200}; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + color: ${DesignTokenColors.neutral700}; + text-align: center; + cursor: pointer; + + &:hover { + background: ${DesignTokenColors.neutral300}; + } + + p { + margin: 8px 0 0 0; + font-size: 14px; + } +`; + +const PlayIcon = styled.div` + font-size: 32px; + color: ${DesignTokenColors.primary700} +`; + +const ModalFooter = styled.div` + display: flex; + justify-content: flex-end; + margin-top: 12px; +`; + +const CloseButton = styled.button` + background: ${DesignTokenColors.primary700}; + border: 1px solid ${DesignTokenColors.primary700}; + border-radius: 9999px; + color: ${DesignTokenColors.whiteUI}; + cursor: pointer; + padding: 10px 18px; + + &:hover{ + background: ${DesignTokenColors.primary800}; + border-color: ${DesignTokenColors.primary800}; + } +`; + +export default OfficeInfoModal; + diff --git a/src/js/components/Ballot/OfficeItemCompressed.jsx b/src/js/components/Ballot/OfficeItemCompressed.jsx index a1831ab0f..9a7f8d1c4 100644 --- a/src/js/components/Ballot/OfficeItemCompressed.jsx +++ b/src/js/components/Ballot/OfficeItemCompressed.jsx @@ -1,8 +1,10 @@ +import { InfoOutlined } from '@mui/icons-material'; import withStyles from '@mui/styles/withStyles'; import withTheme from '@mui/styles/withTheme'; import PropTypes from 'prop-types'; import React, { Component, Suspense } from 'react'; import styled from 'styled-components'; +import DesignTokenColors from '../../common/components/Style/DesignTokenColors'; import OfficeActions from '../../actions/OfficeActions'; import BallotScrollingContainer from './BallotScrollingContainer'; import { BallotScrollingOuterWrapper } from '../../common/components/Style/ScrollingStyles'; @@ -16,6 +18,7 @@ import { sortCandidateList } from '../../utils/positionFunctions'; import { OfficeItemCompressedWrapper, OfficeNameH2 } from '../Style/BallotStyles'; const ShowMoreButtons = React.lazy(() => import(/* webpackChunkName: 'ShowMoreButtons' */ '../Widgets/ShowMoreButtons')); +const OfficeInfoModal = React.lazy(() => import(/* webpackChunkName: 'OfficeInfoModal' */ './OfficeInfoModal')); const NUMBER_OF_CANDIDATES_TO_DISPLAY = 5; // This is related to components/VoterGuide/VoterGuideOfficeItemCompressed @@ -29,6 +32,8 @@ class OfficeItemCompressed extends Component { candidateListForDisplay: [], showAllCandidates: false, totalNumberOfCandidates: 0, + officeInfoModalOpen: false, + moreInfoIconHovered: false, }; } @@ -87,6 +92,22 @@ class OfficeItemCompressed extends Component { }); }; + openOfficeInfoModal = () => { + this.setState({ officeInfoModalOpen: true }); + }; + + closeOfficeInfoModal = () => { + this.setState({ officeInfoModalOpen: false }); + }; + + handleMoreInfoIconHover = () => { + this.setState({ moreInfoIconHovered: true }); + } + + handleMoreInfoIconLeave = () => { + this.setState({ moreInfoIconHovered: false }); + } + goToCandidateLink (candidateWeVoteId) { const { organizationWeVoteId } = this.props; const candidateLink = organizationWeVoteId ? @@ -101,10 +122,11 @@ class OfficeItemCompressed extends Component { let { ballotItemDisplayName } = this.props; const { isFirstBallotItem, officeWeVoteId, primaryParty, useHelpDefeatOrHelpWin } = this.props; // classes - const { candidateListLength, showAllCandidates, totalNumberOfCandidates } = this.state; + const { candidateListLength, showAllCandidates, totalNumberOfCandidates, moreInfoIconHovered } = this.state; ballotItemDisplayName = toTitleCase(ballotItemDisplayName).replace('(Unexpired)', '(Remainder)'); const moreCandidatesToDisplay = candidateListLength > NUMBER_OF_CANDIDATES_TO_DISPLAY; + return (
{ballotItemDisplayName} + {!!primaryParty && ( {' '} @@ -144,6 +172,15 @@ class OfficeItemCompressed extends Component { /> )} + {this.state.officeInfoModalOpen && ( + Loading...
}> + + + )}
); }