Skip to content

Commit

Permalink
feat: floating magic bar (#1269)
Browse files Browse the repository at this point in the history
* feat: floating bar WIP

* feat: scroll to survey results or comments depending on availability

* chore: internationalization

* chore: replace figma colors with the theme ones

* chore: remove forum button

* refactor: useSurvey hook

* chore: floating bar comments loader

* chore: replace svg with png, add opacity animation to floating bar

* fix: always render floating bar
  • Loading branch information
1emu authored Sep 21, 2023
1 parent 9cfe455 commit 914ca00
Show file tree
Hide file tree
Showing 15 changed files with 283 additions and 100 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 62 additions & 0 deletions src/components/FloatingBar/FloatingBar.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
.FloatingBar {
position: sticky;
bottom: 30px;
width: 100%;
height: 48px;
border-radius: 6px;
border: 1px solid var(--black-300);
background: var(--white-900);
box-shadow: 0 0 25px 0 var(--alpha-black-400);
padding: 12px 18px 12px 16px;
display: flex;
justify-content: space-between;
align-items: center;
transition: all 0.5s ease 0s;
opacity: 1;
z-index: 200;
}

.FloatingBar--hidden {
opacity: 0;
z-index: -1;
}

.FloatingBar__ProposalSectionActions {
display: flex;
justify-content: space-between;
padding-right: 24px;
width: 100%;
}

.FloatingBar__Action {
color: var(--black-600);
font-size: 13px;
line-height: 24px;
font-weight: var(--weight-semi-bold);
text-transform: uppercase;
gap: 5px;
display: flex;
align-items: center;
}

.FloatingBar__ReactionsImg {
height: 24px;
width: 36px;
}

.FloatingBar__JoinDiscussion {
min-width: 138px !important;
padding: 0 !important;
display: flex !important;
align-items: center;
gap: 8px;
}

.FloatingBar__LoaderLeft {
left: 32px !important;
}

.FloatingBar__LoaderRight {
margin-right: 84px !important;
position: relative !important;
}
85 changes: 85 additions & 0 deletions src/components/FloatingBar/FloatingBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react'

import classNames from 'classnames'
import { Button } from 'decentraland-ui/dist/components/Button/Button'
import { Loader } from 'decentraland-ui/dist/components/Loader/Loader'

import { forumUrl } from '../../entities/Proposal/utils'
import useFormatMessage from '../../hooks/useFormatMessage'
import useProposalComments from '../../hooks/useProposalComments'
import Link from '../Common/Typography/Link'
import Forum from '../Icon/Forum'
import Open from '../Icon/Open'

import './FloatingBar.css'

const reactions = require('../../images/reactions.png').default

interface FloatingBarProps {
isVisible: boolean
showViewReactions: boolean
scrollToComments: () => void
scrollToReactions: () => void
proposalId?: string
discourseTopicId?: number
discourseTopicSlug?: string
isLoadingProposal: boolean
}

const FloatingBar: React.FC<FloatingBarProps> = ({
proposalId,
discourseTopicId,
discourseTopicSlug,
showViewReactions,
isVisible,
scrollToComments,
scrollToReactions,
isLoadingProposal,
}) => {
const t = useFormatMessage()
const { comments, isLoadingComments } = useProposalComments(proposalId)
const showTotalComments = !isLoadingComments && !isLoadingProposal
return (
<div className={classNames('FloatingBar', !isVisible && !isLoadingProposal && 'FloatingBar--hidden')}>
<div className="FloatingBar__ProposalSectionActions">
{showViewReactions && (
<Link onClick={scrollToReactions} className={'FloatingBar__Action'}>
{t('component.floating_bar.view_reactions_label')}
<img src={reactions} className="FloatingBar__ReactionsImg" />
</Link>
)}
<Link onClick={scrollToComments} className={'FloatingBar__Action'}>
{!showTotalComments && (
<Loader
active
size="tiny"
className={classNames(
!showViewReactions && 'FloatingBar__LoaderLeft',
showViewReactions && 'FloatingBar__LoaderRight'
)}
/>
)}
{showTotalComments && (
<>
<Forum color={'var(--black-600)'} />
{t('component.floating_bar.total_comments_label', { count: comments?.totalComments || 0 })}
</>
)}
</Link>
</div>
<Button
basic
disabled={!discourseTopicId}
target="_blank"
rel="noopener noreferrer"
href={(discourseTopicId && forumUrl(discourseTopicSlug, discourseTopicId)) || ''}
className="FloatingBar__JoinDiscussion"
>
{t('component.floating_bar.forum_label')}
<Open />
</Button>
</div>
)
}

export default FloatingBar
2 changes: 1 addition & 1 deletion src/components/Home/OpenProposal.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid #dddce0;
border-bottom: 1px solid var(--black-300);
}

.OpenProposal__Section {
Expand Down
6 changes: 3 additions & 3 deletions src/components/Icon/Forum.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import React from 'react'

function Forum({ size = 24 }: { size?: number }) {
function Forum({ size = 24, color = 'var(--black-800)' }: { size?: number; color?: string }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} fill="none" viewBox="0 0 24 24">
<path
fill="#000"
fill={color}
fillRule="evenodd"
d="M19.418 6.11h-1.582v-.528c0-.872-.71-1.582-1.582-1.582H4.582C3.71 4 3 4.71 3 5.582v8.438c0 .872.71 1.582 1.582 1.582h1.582v2.636c0 .47.57.703.9.373l.9-.9h5.962l3.01 3.01c.33.33.9.098.9-.373V17.71h1.582C20.29 17.71 21 17 21 16.129V7.69c0-.872-.71-1.582-1.582-1.582zm-12.2 10.855v-1.89a.527.527 0 00-.527-.528H4.582a.528.528 0 01-.527-.527V5.582c0-.29.236-.527.527-.527h11.672c.29 0 .527.236.527.527v8.438c0 .29-.236.527-.527.527H9.855a.527.527 0 00-.372.154l-2.264 2.264zm12.2-.309c.29 0 .527-.236.527-.527V7.69a.528.528 0 00-.527-.527h-1.582v6.856c0 .872-.71 1.582-1.582 1.582h-6.18l-1.055 1.054h5.126c.14 0 .274.056.372.155l2.264 2.264v-1.891c0-.292.236-.528.528-.528h2.109z"
clipRule="evenodd"
></path>
<path
fill="#000"
fill={color}
d="M13.09 7.164H5.637a.527.527 0 000 1.055h7.453a.527.527 0 100-1.055zM15.2 9.273H5.636a.527.527 0 000 1.055h9.562a.527.527 0 100-1.055zM10.98 11.383H5.637a.527.527 0 000 1.055h5.343a.527.527 0 000-1.055z"
></path>
</svg>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Projects/ProjectCard/CliffNotice.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
justify-content: flex-start;
align-items: center;
flex-direction: row;
border: 1px solid #dddce0;
border: 1px solid var(--black-300);
border-radius: 8px;
padding: 0 16px;
min-height: 44px;
Expand Down
26 changes: 15 additions & 11 deletions src/components/Proposal/Comments/ProposalComments.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import React from 'react'
import React, { Ref, forwardRef } from 'react'

import { ProposalAttributes } from '../../../entities/Proposal/types'
import useProposalComments from '../../../hooks/useProposalComments'
import Comments from '../../Comments/Comments'

type ProposalComments = {
type ProposalCommentsProps = {
proposal: ProposalAttributes | null
}

export default function ProposalComments({ proposal }: ProposalComments) {
const ProposalComments = forwardRef(({ proposal }: ProposalCommentsProps, ref: Ref<HTMLDivElement>) => {
const { comments, isLoadingComments } = useProposalComments(proposal?.id)

return (
<Comments
comments={comments}
isLoading={isLoadingComments}
topicId={proposal?.discourse_topic_id}
topicSlug={proposal?.discourse_topic_slug}
topicType="proposal"
/>
<div ref={ref}>
<Comments
comments={comments}
isLoading={isLoadingComments}
topicId={proposal?.discourse_topic_id}
topicSlug={proposal?.discourse_topic_slug}
topicType="proposal"
/>
</div>
)
}
})

export default ProposalComments
2 changes: 0 additions & 2 deletions src/components/Proposal/ProposalSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { ProposalPageState } from '../../pages/proposal'
import CalendarAlertModal from '../Modal/CalendarAlertModal'

import CalendarAlertButton from './View/CalendarAlertButton'
import ForumButton from './View/ForumButton'
import ProposalCoAuthorStatus from './View/ProposalCoAuthorStatus'
import ProposalDetailSection from './View/ProposalDetailSection'
import ProposalGovernanceSection from './View/ProposalGovernanceSection'
Expand Down Expand Up @@ -152,7 +151,6 @@ export default function ProposalSidebar({
{showProposalThresholdsSummary && (
<ProposalThresholdsSummary proposal={proposal} partialResults={partialResults} />
)}
<ForumButton loading={proposalLoading} proposal={proposal} />
<SubscribeButton
loading={proposalLoading || subscriptionsLoading || subscribing}
disabled={!proposal}
Expand Down
37 changes: 16 additions & 21 deletions src/components/Proposal/SentimentSurvey/SurveyResults.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react'
import React, { Ref, forwardRef, useMemo } from 'react'

import { decodeSurvey } from '../../../entities/SurveyTopic/decoder'
import { Reaction, Topic, TopicFeedback } from '../../../entities/SurveyTopic/types'
Expand All @@ -10,7 +10,6 @@ import SurveyTopicResult from './SurveyTopicResult'

interface Props {
votes: Record<string, Vote> | null
isLoadingVotes: boolean
surveyTopics: Topic[] | null
isLoadingSurveyTopics: boolean
}
Expand Down Expand Up @@ -51,30 +50,26 @@ function getResults(availableTopics: Topic[] | null, votes: Record<string, Vote>
return topicsResults
}

const SurveyResults = ({ votes, isLoadingVotes, surveyTopics, isLoadingSurveyTopics }: Props) => {
const SurveyResults = forwardRef(({ votes, surveyTopics, isLoadingSurveyTopics }: Props, ref: Ref<HTMLDivElement>) => {
const t = useFormatMessage()
const topicResults = useMemo(() => getResults(surveyTopics, votes), [surveyTopics, votes])
const topicIds = Object.keys(topicResults)
const hasVotes = votes && Object.keys(votes).length > 0 && !isLoadingVotes
const hasSurveyTopics = surveyTopics && surveyTopics?.length > 0 && !isLoadingSurveyTopics

if (!hasVotes || !hasSurveyTopics) {
return null
}

return (
<Section title={t('survey.results.title')} isLoading={isLoadingSurveyTopics}>
{topicIds.map((topicId: string, index: number) => {
return (
<SurveyTopicResult
key={`SurveyTopicResult__${index}`}
topicId={topicId}
topicResult={topicResults[topicId]}
/>
)
})}
</Section>
<div ref={ref}>
<Section title={t('survey.results.title')} isLoading={isLoadingSurveyTopics}>
{topicIds.map((topicId: string, index: number) => {
return (
<SurveyTopicResult
key={`SurveyTopicResult__${index}`}
topicId={topicId}
topicResult={topicResults[topicId]}
/>
)
})}
</Section>
</div>
)
}
})

export default SurveyResults
2 changes: 1 addition & 1 deletion src/components/Proposal/Update/ProposalUpdate.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
justify-content: space-between;
align-items: center;
flex-direction: row;
border: 1px solid #dddce0;
border: 1px solid var(--black-300);
border-radius: 8px;
padding: 0 16px;
min-height: 44px;
Expand Down
27 changes: 0 additions & 27 deletions src/components/Proposal/View/ForumButton.tsx

This file was deleted.

25 changes: 25 additions & 0 deletions src/hooks/useSurvey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ProposalAttributes } from '../entities/Proposal/types'
import { Vote } from '../entities/Votes/types'

import useSurveyTopics from './useSurveyTopics'

const SURVEY_FEATURE_LAUNCH = new Date(2023, 3, 5, 0, 0)

export default function useSurvey(
proposal?: ProposalAttributes | null,
votes?: Record<string, Vote> | null,
isLoadingVotes?: boolean,
isMobile?: boolean
) {
const { surveyTopics, isLoadingSurveyTopics } = useSurveyTopics(proposal?.id)
const voteWithSurvey =
!isLoadingSurveyTopics &&
!!surveyTopics &&
surveyTopics.length > 0 &&
!!proposal &&
proposal.created_at > SURVEY_FEATURE_LAUNCH
const hasVotes = votes && Object.keys(votes).length > 0 && !isLoadingVotes
const hasSurveyTopics = surveyTopics && surveyTopics?.length > 0 && !isLoadingSurveyTopics
const showSurveyResults = proposal && voteWithSurvey && !isMobile && hasVotes && hasSurveyTopics
return { surveyTopics, isLoadingSurveyTopics, voteWithSurvey, showSurveyResults }
}
Binary file added src/images/reactions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion src/intl/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,11 @@
"duplicated_error": "It looks like this {addressAlias} has already been added.",
"max_error": "The maximum number of {addressesAlias} was exceeded.",
"logged_user_invalid": "Logged user address is not allowed"
},
"floating_bar": {
"view_reactions_label": "View Reactions",
"total_comments_label": "{count} {count, plural, one {Comment} other {Comments}}",
"forum_label": "Join Discussion"
}
},
"page": {
Expand Down Expand Up @@ -963,7 +968,6 @@
"proposal_detail": {
"title": "Decentraland DAO",
"description": "The governance hub for Decentraland. Create and vote on proposals that help shape the future of the metaverse.",
"forum_button": "Discuss in the forum",
"subscribe_button": "Add to my Watchlist",
"subscribed_button": "Remove from my Watchlist",
"calendar_button": "Set calendar alert",
Expand Down
Loading

0 comments on commit 914ca00

Please sign in to comment.