Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Box } from '@mui/material';
import Skeleton from '@mui/material/Skeleton';
import withStyles from '@mui/styles/withStyles';
import PropTypes from 'prop-types';
import React from 'react';
Expand Down Expand Up @@ -178,6 +180,11 @@ class CampaignSupportThermometer extends React.Component {
}
const showEncouragementToSupport = !!(supportersCountNextGoal && !inCompressedMode && !finalElectionDateInPast && !voterOpposesCampaignX);

// Reserve full layout space while campaign data loads to avoid downward layout shift
const campaignX = campaignXWeVoteId ? CampaignStore.getCampaignXByWeVoteId(campaignXWeVoteId) : null;
const campaignDataLoaded = !!(campaignX?.campaignx_we_vote_id);
const showLoadingSkeleton = campaignXWeVoteId && !campaignDataLoaded && !inCompressedMode;

// console.log('CampaignSupportThermometer render: campaignXWeVoteId:', campaignXWeVoteId, ', supportersCount:', supportersCount, ', supportersCountNextGoal:', supportersCountNextGoal);
return (
<CampaignSupportThermometerWrapper>
Expand All @@ -186,24 +193,31 @@ class CampaignSupportThermometer extends React.Component {
<HeartFavoriteToggleLoader campaignXWeVoteId={campaignXWeVoteId} />
</HeartWrapper>
<HeartDetailsWrapper>
{showEncouragementToSupport && (
<SupportersText inCompressedMode={inCompressedMode}>
{supportersText}
</SupportersText>
)}
{showEncouragementToSupport && (
<GoalText>
{' '}
Help them get to
{' '}
{numberWithCommas(supportersCountNextGoal)}
!
</GoalText>
{showLoadingSkeleton ? (
<Box flex={1}>
<Skeleton variant="text" width="70%" height={18} sx={{ mb: 0.5 }} />
<Skeleton variant="text" width="50%" height={18} />
</Box>
) : showEncouragementToSupport && (
<>
<SupportersText inCompressedMode={inCompressedMode}>
{supportersText}
</SupportersText>
<GoalText>
{' '}
Help them get to
{' '}
{numberWithCommas(supportersCountNextGoal)}
!
</GoalText>
</>
)}
</HeartDetailsWrapper>
</HeartPlusDetailsWrapper>
<ProgressBarWrapper>
{showEncouragementToSupport && (
{showLoadingSkeleton ? (
<Skeleton variant="rounded" width="100%" height={12} sx={{ borderRadius: 6, mb: 1.5 }} />
) : showEncouragementToSupport && (
<ProgressBar percentage={percentageForDisplay}>
<span id="progress-bar" />
<span id="right-arrow" />
Expand Down Expand Up @@ -283,6 +297,8 @@ const HeartPlusDetailsWrapper = styled('div')`
`;

const HeartDetailsWrapper = styled('div')`
flex: 1;
min-width: 0;
`;

const HeartWrapper = styled('div')`
Expand Down
53 changes: 42 additions & 11 deletions src/js/common/components/CardForListBody.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { HowToVote, Launch } from '@mui/icons-material';
import { Box } from '@mui/material';
import Skeleton from '@mui/material/Skeleton';
import withStyles from '@mui/styles/withStyles';
import PropTypes from 'prop-types';
import React, { Suspense } from 'react';
Expand Down Expand Up @@ -91,7 +93,7 @@ function CardForListBody (props) {
{highlightSearchText(ballotItemDisplayName, searchText)}
{showPoliticianOpenInNewWindow && (
<LaunchIconWrapper>
<Suspense fallback={<></>}>
<Suspense fallback={<Skeleton variant="rounded" width={16} height={14} sx={{ display: 'inline-block', borderRadius: 0.5 }} />}>
<OpenExternalWebSite
linkIdAttribute="openPoliticianPage"
url={politicianDetailsURL}
Expand Down Expand Up @@ -143,26 +145,50 @@ function CardForListBody (props) {
</YearAndHeartDiv>
<SpaceBeforeThermometer />
{useCampaignSupportThermometer && (
<Suspense fallback={<span>&nbsp;</span>}>
<Suspense fallback={(
<Box>
<Box display="flex" gap={1} sx={{ mb: 1 }}>
<Skeleton variant="rounded" width={60} height={36} sx={{ borderRadius: 2 }} />
<Box flex={1} sx={{ minWidth: 0 }}>
<Skeleton variant="text" width="70%" height={18} sx={{ mb: 0.5 }} />
<Skeleton variant="text" width="50%" height={18} />
</Box>
</Box>
<Skeleton variant="rounded" width="100%" height={12} sx={{ borderRadius: 6 }} />
</Box>
)}>
<CampaignSupportThermometer
campaignXWeVoteId={linkedCampaignXWeVoteId}
finalElectionDateInPast={finalElectionDateInPast}
/>
</Suspense>
)}
<CardRowsWrapper>
{politicalParty && (
<CardForListRow>
<CardForListRow>
<FlexDivLeft>
<SvgImageWrapper>
{politicalParty ? (
<SvgImage imageName={politicalPartySvgNameWithPath} stylesTextIncoming="width: 18px;" />
) : (
<Skeleton variant="rounded" width={18} height={18} sx={{ borderRadius: 0.5 }} />
)}
</SvgImageWrapper>
{politicalParty ? (
<PoliticalPartyDiv>{highlightSearchText(politicalParty, searchText)}</PoliticalPartyDiv>
) : (
<Skeleton variant="text" width={80} height={14} />
)}
</FlexDivLeft>
</CardForListRow>
<CardForListRow>
<Suspense fallback={(
<FlexDivLeft>
<SvgImageWrapper>
<SvgImage imageName={politicalPartySvgNameWithPath} stylesTextIncoming="width: 18px;" />
<Skeleton variant="rounded" width={18} height={18} sx={{ borderRadius: 0.5 }} />
</SvgImageWrapper>
<PoliticalPartyDiv>{highlightSearchText(politicalParty, searchText)}</PoliticalPartyDiv>
<Skeleton variant="text" width="80%" height={14} />
</FlexDivLeft>
</CardForListRow>
)}
<CardForListRow>
<Suspense fallback={<></>}>
)}>
{useOfficeHeld ? (
<FlexDivLeft>
{(districtName || officeName) && (
Expand Down Expand Up @@ -283,7 +309,12 @@ function CardForListBody (props) {
</TitleAndTextWrapper>
{!hideItemActionBar && (
<CampaignActionButtonsWrapper>
<Suspense fallback={<></>}>
<Suspense fallback={(
<Box display="flex" gap={1} justifyContent="center">
<Skeleton variant="rounded" width={100} height={36} sx={{ borderRadius: 2 }} />
<Skeleton variant="rounded" width={100} height={36} sx={{ borderRadius: 2 }} />
</Box>
)}>
{(finalElectionDateInPast || usePoliticianWeVoteIdForBallotItem) ? (
<ItemActionBar
ballotItemWeVoteId={candidateWeVoteId}
Expand Down
151 changes: 151 additions & 0 deletions src/js/common/components/CardForListBodySkeleton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Box } from '@mui/material';
import Skeleton from '@mui/material/Skeleton';
import PropTypes from 'prop-types';
import React from 'react';
import styled from 'styled-components';
import isMobileScreenSize from '../utils/isMobileScreenSize';
import {
CampaignActionButtonsWrapper,
CandidateCardForListWrapper,
CardForListRow,
CardRowsWrapper,
OneCampaignInnerWrapper,
OneCampaignOuterWrapper,
OneCampaignPhotoDesktopColumn,
OneCampaignPhotoWrapperMobile,
OneCampaignTextColumn,
TitleAndTextWrapper,
} from './Style/CampaignCardStyles';
import { renderLog } from '../utils/logging';

const SpaceBeforeThermometer = styled('div')`
margin-bottom: 8px;
`;

const YearAndHeartDiv = styled('div')`
display: flex;
justify-content: space-between;
`;

const FlexDivLeft = styled('div')`
align-items: center;
display: flex;
justify-content: start;
`;

const SvgImageWrapper = styled('div')`
max-width: 25px;
min-width: 25px;
width: 25px;
`;

function CardForListBodySkeleton (props) {
renderLog('CardForListBodySkeleton');
const { hideCardMargins, hideItemActionBar, limitCardWidth, showPoliticianOpenInNewWindow, useVerticalCard } = props;
const useVerticalLayout = limitCardWidth || useVerticalCard || isMobileScreenSize();

return (
<CandidateCardForListWrapper limitCardWidth={limitCardWidth}>
<OneCampaignOuterWrapper limitCardWidth={limitCardWidth}>
<OneCampaignInnerWrapper
hideCardMargins={hideCardMargins}
useVerticalCard={useVerticalLayout}
>
<OneCampaignTextColumn hideCardMargins={hideCardMargins}>
<TitleAndTextWrapper hideCardMargins={hideCardMargins}>
{/* State */}
<Skeleton variant="text" width={80} height={12} sx={{ mb: 0.5 }} />
{/* Name + optional Launch icon */}
<Box display="flex" alignItems="center" gap={0.5} sx={{ mb: 0.5 }}>
<Skeleton variant="text" width={200} height={24} />
{showPoliticianOpenInNewWindow && (
<Skeleton variant="rounded" width={14} height={14} sx={{ borderRadius: 0.5 }} />
)}
</Box>
{/* Year + Heart */}
<YearAndHeartDiv>
<Skeleton variant="text" width={36} height={12} />
{!useVerticalLayout && (
<Skeleton variant="rounded" width={60} height={36} sx={{ borderRadius: 2 }} />
)}
</YearAndHeartDiv>
<SpaceBeforeThermometer />
{/* Thermometer */}
{useVerticalLayout && (
<>
<Box display="flex" gap={1} sx={{ mb: 1 }}>
<Skeleton variant="rounded" width={60} height={36} sx={{ borderRadius: 2 }} />
<Box flex={1} sx={{ minWidth: 0 }}>
<Skeleton variant="text" width="70%" height={18} sx={{ mb: 0.5 }} />
<Skeleton variant="text" width="50%" height={18} />
</Box>
</Box>
<Skeleton variant="rounded" width="100%" height={12} sx={{ borderRadius: 6, mb: 1 }} />
</>
)}
{/* Party + Office rows (match CardForListBody: SvgImageWrapper 25px + icon 18px + text) */}
<CardRowsWrapper>
<CardForListRow>
<FlexDivLeft>
<SvgImageWrapper>
<Skeleton variant="rounded" width={18} height={18} sx={{ borderRadius: 0.5 }} />
</SvgImageWrapper>
<Skeleton variant="text" width="60%" height={14} />
</FlexDivLeft>
</CardForListRow>
<CardForListRow>
<FlexDivLeft>
<SvgImageWrapper>
<Skeleton variant="rounded" width={18} height={18} sx={{ borderRadius: 0.5 }} />
</SvgImageWrapper>
<Skeleton variant="text" width="80%" height={14} />
</FlexDivLeft>
</CardForListRow>
</CardRowsWrapper>
</TitleAndTextWrapper>
{!hideItemActionBar && (
<CampaignActionButtonsWrapper>
<Box display="flex" gap={1} justifyContent="center">
<Skeleton variant="rounded" width={100} height={36} sx={{ borderRadius: 2 }} />
<Skeleton variant="rounded" width={100} height={36} sx={{ borderRadius: 2 }} />
</Box>
</CampaignActionButtonsWrapper>
)}
</OneCampaignTextColumn>
{/* Mobile image */}
<OneCampaignPhotoWrapperMobile className="u-show-mobile">
<Skeleton
variant="rounded"
width="100%"
height={157}
sx={{ borderRadius: 2 }}
/>
</OneCampaignPhotoWrapperMobile>
{/* Desktop image */}
<OneCampaignPhotoDesktopColumn
className="u-show-desktop-tablet"
hideCardMargins={hideCardMargins}
limitCardWidth={limitCardWidth}
useVerticalCard={useVerticalLayout}
>
<Skeleton
variant="rounded"
width={limitCardWidth ? 276 : useVerticalLayout ? '100%' : 224}
height={limitCardWidth ? 157 : useVerticalLayout ? 200 : 117}
sx={{ borderRadius: 2 }}
/>
</OneCampaignPhotoDesktopColumn>
</OneCampaignInnerWrapper>
</OneCampaignOuterWrapper>
</CandidateCardForListWrapper>
);
}
CardForListBodySkeleton.propTypes = {
hideCardMargins: PropTypes.bool,
hideItemActionBar: PropTypes.bool,
limitCardWidth: PropTypes.bool,
showPoliticianOpenInNewWindow: PropTypes.bool,
useVerticalCard: PropTypes.bool,
};

export default CardForListBodySkeleton;
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { Suspense, useEffect } from 'react';
import PropTypes from 'prop-types';
import Skeleton from '@mui/material/Skeleton';
import styled from 'styled-components';
import CampaignActions from '../../../actions/CampaignActions';
import VoterGuideActions from '../../../../actions/VoterGuideActions';
Expand Down Expand Up @@ -36,9 +37,11 @@ function HeartFavoriteToggleLoader ({ campaignXWeVoteId, organizationWeVoteId })
}
}, [organizationWeVoteId]);

const heartToggleSkeleton = <Skeleton variant="rounded" width={60} height={36} sx={{ borderRadius: 2 }} />;

return (
<HeartFavoriteToggleLoaderContainer>
<Suspense fallback={<></>}>
<Suspense fallback={heartToggleSkeleton}>
<Suspense fallback={<HeartFavoriteToggleBase />}>
<HeartFavoriteToggleLive campaignXWeVoteId={campaignXWeVoteId} organizationWeVoteId={organizationWeVoteId} />
</Suspense>
Expand Down
22 changes: 11 additions & 11 deletions src/js/components/PoliticianListRoot/PoliticianCardForList.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component, Suspense } from 'react';
import CardForListBodyPlaceholder from '../../common/components/CardForListBodyPlaceholder';
import CardForListBodySkeleton from '../../common/components/CardForListBodySkeleton';
import { getTodayAsInteger } from '../../common/utils/dateFormat';
import { renderLog } from '../../common/utils/logging';
import CampaignSupporterStore from '../../common/stores/CampaignSupporterStore';
Expand Down Expand Up @@ -161,11 +161,12 @@ class PoliticianCardForList extends Component {
const { campaignSupported, candidate, candidateWeVoteId, linkedCampaignXWeVoteId, politician } = this.state;
if (!politicianWeVoteId) {
return (
<CardForListBodyPlaceholder
<CardForListBodySkeleton
useVerticalCard={useVerticalCard}
hideCardMargins
profileImageBackgroundColor
limitCardWidth={limitCardWidth}
hideItemActionBar={hideItemActionBar}
showPoliticianOpenInNewWindow={showPoliticianOpenInNewWindow}
/>
);
}
Expand Down Expand Up @@ -238,14 +239,13 @@ class PoliticianCardForList extends Component {
const finalElectionDateInPast = candidateUltimateElectionDate && (candidateUltimateElectionDate <= todayAsInteger);
const pathToUseToKeepHelping = this.getPathToUseToKeepHelping();
const fallbackJsx = (
<span>
<CardForListBodyPlaceholder
useVerticalCard
hideCardMargins
limitCardWidth
profileImageBackgroundColor
/>
</span>
<CardForListBodySkeleton
useVerticalCard
hideCardMargins
limitCardWidth={limitCardWidth}
hideItemActionBar={hideItemActionBar}
showPoliticianOpenInNewWindow={showPoliticianOpenInNewWindow}
/>
);
return (
<Suspense fallback={fallbackJsx}>
Expand Down
Loading
Loading