From 1bdf2931a64ed53b25239cdda96481bf9ec4ac33 Mon Sep 17 00:00:00 2001 From: PeterJFB Date: Tue, 3 May 2022 19:57:03 +0200 Subject: [PATCH] Redesign poll component --- app/components/Poll/Poll.css | 250 +++++++------- app/components/Poll/index.js | 394 ++++++++++------------ app/routes/polls/components/PollDetail.js | 1 + app/styles/variables.css | 2 +- 4 files changed, 304 insertions(+), 343 deletions(-) diff --git a/app/components/Poll/Poll.css b/app/components/Poll/Poll.css index 5e955a4d6c..89de452439 100644 --- a/app/components/Poll/Poll.css +++ b/app/components/Poll/Poll.css @@ -1,185 +1,167 @@ -@import '~app/styles/variables.css'; - -.optionWrapper { - justify-content: center; - min-height: 120px; - cursor: pointer; - position: relative; -} - -.pollTable { +.poll { + margin: 0 auto; + background-color: var(--lego-card-color); width: 100%; - font-size: 14px; -} - -.pollTable td { - border: 0; - padding: 5px; + min-width: 250px; + max-width: 500px; + overflow-y: hidden; + border-radius: 15px; + transition: 2s height; } -.pollTable .textColumn { - border-right: 1px solid #c5c5c5; - text-align: right; - padding-right: 13px; - line-height: 16px; -} - -.pollTable .graphColumn { - width: auto; - min-width: 200px; - padding-left: 13px; +.topBar { + background-color: var(--lego-red); + position: relative; + width: 100%; + height: 70px; + box-shadow: 0 4px 4px rgba(0, 0, 0, 25%); } -.poll { - composes: withShadow from '~app/styles/utilities.css'; - background: var(--lego-card-color); - padding: 15px 20px 8px; +.stats { + color: var(--lego-card-color); + margin-top: 17px; + transform: scale(150%); } -.pollLight { - background: var(--lego-card-color); +.notAnswered { + color: var(--color-white); + font-size: 30px; + font-weight: 900; + line-height: 45px; } -.noVotes { - font-style: italic; +.headerBar { + color: var(--lego-font-color); + background-color: var(--lego-card-color); + font-weight: 900; + text-align: center; + position: absolute; + bottom: 0; + left: 50%; + width: 80%; + height: 50px; + border-radius: 10px; + box-shadow: 0 4px 4px rgba(0, 0, 0, 25%); + transform: translate(-50%, 50%); } -.pollGraph { - animation: graph 1.2s cubic-bezier(41%, 80%, 40%, 94%); - background-color: var(--lego-red-color); - padding-left: 8px; - border-radius: 0 2px 2px 0; - font-style: italic; - font-weight: 300; - color: var(--color-white); - height: 30px; +.contentWrapper { + overflow-y: hidden; + width: 100%; + transition: 0.5s height; } -.fullGraph { - background-color: #e7e7e7; - width: 100%; +.voteOptionsWrapper { display: flex; + flex-direction: column; + align-items: center; + width: 100%; + height: fit-content; } -html[data-theme='dark'] .fullGraph { +.voteButton { color: var(--color-white); - background-color: var(--color-mono-gray-5); -} - -html[data-theme='dark'] .pollGraph { - color: var(--color-black); -} - -.pollGraph span { - vertical-align: middle; -} - -@keyframes graph { - from { - width: 1px; - } + background: var(--lego-red); + font-weight: 500; + width: 90%; + height: 37px; + padding: 5px; + margin: 2px; - to { - width: 100%; + /* Override default components/Button property */ + + .voteButton { + margin-left: 2px; } } -.pollHeader { - border-radius: 8px; - margin-bottom: 20px; - margin-left: 20px; - font-size: 16px; - color: var(--lego-font-color); -} - -.voteButton { - background: var(--lego-red-color); - color: var(--lego-color-gray-light); - border: 1px solid var(--border-gray); +.voteOptions { width: 100%; - margin: 0 !important; - font-size: 15px; - max-width: 400px; + padding-top: 35px; } -.voteButton:hover { - opacity: 0.8; -} - -html[data-theme='dark'] .voteButton { - color: var(--color-dark-gray-3); +.bottomInfoWrapper { + display: flex; + flex-direction: column; + align-items: center; } -.moreOptionsLink { - justify-content: space-between; +.totalVotesInfo { + display: flex; } -.arrow { - margin-top: 9px; - cursor: pointer; - - &:hover { - transform: scale(1.5); - color: var(--color-red-3); - transition: transform 0.2s; - } +.resultsHiddenInfo { + font-style: italic; } -.blurContainer { - display: none; - position: absolute; - justify-content: center; +.pollTable { width: 100%; - height: 100%; + font-size: 14px; } -.blurOverlay { - position: absolute; - z-index: 2; - color: var(--color-black); - margin-top: 25px; +.pollTable tr { + width: 100%; } -.optionWrapper:hover .blurContainer { - display: flex; +.pollTable td { + border: 0; + padding: 5px; } -.optionWrapper:hover .blurEffect { - filter: blur(3px); - pointer-events: none; +.pollTable .textColumn { + text-align: right; + line-height: 16px; + word-wrap: break-word; + width: fit-content; + max-width: 200px; + padding-right: 13px; + border-right: 3px solid #c5c5c5; } -.blurArrow { - margin-top: 40px; +.pollTable .graphColumn { + width: auto; + min-width: 150px; + padding-left: 13px; + padding-right: 15px; } -.alignItems { +.fullGraph { display: flex; - justify-content: center; + background-color: var(--color-mono-gray-5); + color: #000; + word-wrap: break-word; + width: 100%; } -.answered { - margin: 15px 0; - text-align: center; - font-weight: bold; +.pollGraph { + background-color: var(--lego-red); + font-style: italic; + font-weight: 300; + color: var(--color-white); + height: 30px; + padding-left: 8px; + animation: graph 1.2s cubic-bezier(0.41, 0.8, 0.4, 0.94); + border-radius: 0 2px 2px 0; + box-shadow: 0 4px 4px rgba(0, 0, 0, 25%); } -.bottomInfo { - display: flex; - justify-content: space-between; +.bottomBar { + width: 100%; + height: 70px; + background-color: var(--lego-red); } -.resultsHidden { - font-style: italic; +.arrowUp, +.arrowDown { + text-shadow: 0 2px 2px rgba(0, 0, 0, 25%); + transform: scale(250%); + color: var(--lego-card-color); + transition: 0.8s margin-bottom; } -@media (--mobile-device) { - .blurContainer { - display: flex; - } - - .blurEffect { - filter: blur(3px); - pointer-events: none; - } +.arrowUp { + margin-bottom: 25px; } + +.arrowDown { + margin-bottom: 10px; +} \ No newline at end of file diff --git a/app/components/Poll/index.js b/app/components/Poll/index.js index e176624232..b75e3a08d1 100644 --- a/app/components/Poll/index.js +++ b/app/components/Poll/index.js @@ -1,6 +1,6 @@ // @flow -import { Component } from 'react'; +import React, { useRef, useState } from 'react'; import Button from 'app/components/Button'; import styles from './Poll.css'; import type { PollEntity, OptionEntity } from 'app/reducers/polls'; @@ -9,247 +9,225 @@ import { Link } from 'react-router-dom'; import Icon from 'app/components/Icon'; import { Flex } from 'app/components/Layout'; import Tooltip from 'app/components/Tooltip'; -import cx from 'classnames'; type Props = { poll: PollEntity, handleVote: (pollId: number, optionId: number) => Promise<*>, allowedToViewHiddenResults?: boolean, backgroundLight?: boolean, - truncate?: number, details?: boolean, + expanded?: boolean, + alwaysOpen?: boolean, }; type OptionEntityRatio = OptionEntity & { ratio: number, }; -type State = { - truncateOptions: boolean, - shuffledOptions: Array, - expanded: boolean, +// As described in: https://stackoverflow.com/questions/13483430/how-to-make-rounded-percentages-add-up-to-100 +const perfectRatios = ( + options: $ReadOnlyArray +): OptionEntityRatio[] => { + const off = + 100 - options.reduce((a, option) => a + Math.floor(option.ratio), 0); + return sortBy( + options, + (o: OptionEntityRatio) => Math.floor(o.ratio) - o.ratio + ) + .map((option: OptionEntityRatio, index: number) => { + return { + ...option, + ratio: Math.floor(option.ratio) + (index < off ? 1 : 0), + }; + }) + .sort((a, b) => b.ratio - a.ratio); }; -class Poll extends Component { - constructor(props: Props) { - super(props); - const options = this.optionsWithPerfectRatios(props.poll.options); - const shuffledOptions = this.shuffle(options); - if (props.truncate && options.length > props.truncate) { - this.state = { - truncateOptions: true, - shuffledOptions: shuffledOptions, - expanded: false, - }; - } else { - this.state = { - truncateOptions: false, - shuffledOptions: shuffledOptions, - expanded: true, - }; - } +const optionsWithPerfectRatios = (options: Array) => { + const totalVotes = options.reduce((a, option) => a + option.votes, 0); + const ratios = options.map((option) => { + return { ...option, ratio: (option.votes / totalVotes) * 100 }; + }); + return perfectRatios(ratios); +}; + +const shuffle = (array: Array) => { + const oldArray = array.slice(0); + const newArray = []; + for (let i = 0; i < array.length; i++) { + const randIndex = Math.floor(Math.random() * oldArray.length); + newArray[i] = oldArray[randIndex]; + oldArray.splice(randIndex, 1); } - toggleTruncate = () => { - this.setState({ - expanded: !this.state.expanded, - }); - }; + return newArray; +}; - optionsWithPerfectRatios = (options: Array) => { - const totalVotes = options.reduce((a, option) => a + option.votes, 0); - const ratios = options.map((option) => { - return { ...option, ratio: (option.votes / totalVotes) * 100 }; - }); - return this.perfectRatios(ratios); - }; +const Poll = ({ + poll, + handleVote, + backgroundLight, + details, + allowedToViewHiddenResults, + alwaysOpen = false, + expanded = false, +}: Props) => { + const { id, title, description, hasAnswered, totalVotes, resultsHidden } = + poll; + const options = optionsWithPerfectRatios(poll.options); + const shuffledOptions = shuffle(options); + const [isExpanded, setIsExpanded] = useState(expanded || alwaysOpen); - // As described in: https://stackoverflow.com/questions/13483430/how-to-make-rounded-percentages-add-up-to-100 - perfectRatios = ( - options: $ReadOnlyArray - ): OptionEntityRatio[] => { - const off = - 100 - options.reduce((a, option) => a + Math.floor(option.ratio), 0); - return sortBy( - options, - (o: OptionEntityRatio) => Math.floor(o.ratio) - o.ratio - ) - .map((option: OptionEntityRatio, index: number) => { - return { - ...option, - ratio: Math.floor(option.ratio) + (index < off ? 1 : 0), - }; - }) - .sort((a, b) => b.ratio - a.ratio); + const toggleTruncate = () => { + setIsExpanded(!isExpanded); }; - shuffle = (array: Array) => { - const oldArray = array.slice(0); - const newArray = []; - for (let i = 0; i < array.length; i++) { - const randIndex = Math.floor(Math.random() * oldArray.length); - newArray[i] = oldArray[randIndex]; - oldArray.splice(randIndex, 1); - } - - return newArray; - }; + const optionsRef = useRef(null); - render() { - const { - poll, - handleVote, - backgroundLight, - details, - truncate, - allowedToViewHiddenResults, - } = this.props; - const { truncateOptions, expanded, shuffledOptions } = this.state; - const { id, title, description, hasAnswered, totalVotes, resultsHidden } = - poll; - const options = this.optionsWithPerfectRatios(this.props.poll.options); - const orderedOptions = hasAnswered ? options : shuffledOptions; - const optionsToShow = expanded - ? orderedOptions - : orderedOptions.slice(0, truncate); - const showResults = !resultsHidden || allowedToViewHiddenResults; + const orderedOptions = hasAnswered ? options : shuffledOptions; + const optionsToShow = isExpanded ? orderedOptions : orderedOptions; + const showResults = !resultsHidden || allowedToViewHiddenResults; - return ( -
- - - - {title} - - - - - - {details && ( -
-

{description}

-
+ return ( + + + {hasAnswered ? ( + + ) : ( +
?
)} - {hasAnswered && !showResults && ( -
- Du har svart - -
- )} - {hasAnswered && showResults && ( - - - - {optionsToShow.map(({ id, name, votes, ratio }) => { - return ( - - - - - ); - })} - -
{name} - {votes === 0 ? ( - Ingen stemmer - ) : ( -
-
-
- {ratio >= 18 && {`${ratio}%`}} -
-
- {ratio < 18 && ( - - {`${ratio}%`} - - )} -
- )} -
- {resultsHidden && ( -

- Resultatet er skjult for vanlige brukere. -

+ + + {!details && description.length !== 0 ? ( + + {title} + + ) : ( + <> {title} )} - )} - {!hasAnswered && ( - - {!expanded && ( - -

- Klikk her for å se alle alternativene. -

- -
- )} - {options && - optionsToShow.map((option) => ( - + + + +
+ {!hasAnswered && ( + + {details && description} + {options && + optionsToShow.map((option) => ( - - ))} - - )} -
-
- {truncateOptions && - (!hasAnswered || - !resultsHidden || - allowedToViewHiddenResults) && ( -
- -
- )} -
-
- {`Stemmer: ${totalVotes}`} - {hasAnswered && !showResults && ( - - Resultatet er skjult. - + ))} + + )} + {hasAnswered && !showResults && ( + + {details && description} +
+ Resultatet er skjult +
+
+ )} + {hasAnswered && showResults && ( + + {details && description} + + + {optionsToShow.map(({ id, name, votes, ratio }) => { + return ( + + + + + ); + })} + +
{name} + {votes === 0 ? ( + + Ingen stemmer + + ) : ( +
+
+
+ {ratio >= 18 && {`${ratio}%`}} +
+
+ {ratio < 18 && ( + + {`${ratio}%`} + + )} +
+ )} +
+
+ )} +
+
+ + + + Stemmer: {totalVotes} +
+ {resultsHidden && ( +
+ Resultatet er skjult for vanlige brukere. +
)}
-
- ); - } -} + + + {!alwaysOpen ? ( + + ) : ( + + )} + + + ); +}; export default Poll; diff --git a/app/routes/polls/components/PollDetail.js b/app/routes/polls/components/PollDetail.js index 168f518f3a..1f310f4ab0 100644 --- a/app/routes/polls/components/PollDetail.js +++ b/app/routes/polls/components/PollDetail.js @@ -52,6 +52,7 @@ class PollDetail extends Component { handleVote={this.props.votePoll} allowedToViewHiddenResults={this.props.actionGrant.includes('edit')} details + alwaysOpen /> )} {this.state.editing && ( diff --git a/app/styles/variables.css b/app/styles/variables.css index 65a1539c86..cbe6cef125 100644 --- a/app/styles/variables.css +++ b/app/styles/variables.css @@ -113,7 +113,7 @@ --lego-red: #c0392b; --lego-max-width: 1100px; --lego-default-padding: 2rem; - --lego-card-color: #262626; + --lego-card-color: #262626; /* TODO: use placard-color instead of card-color on Poll.css */ --lego-footer-color: #e21617; --lego-dark-red-color: #b21c17; --lego-red-color: #c0392b;