]> Untitled Git - lemmy-ui.git/blob - src/shared/components/common/vote-buttons.tsx
feat: Move vote buttons to separate component
[lemmy-ui.git] / src / shared / components / common / vote-buttons.tsx
1 import { showScores } from "@utils/app";
2 import { numToSI } from "@utils/helpers";
3 import { Component, linkEvent } from "inferno";
4 import { CommentAggregates, PostAggregates } from "lemmy-js-client";
5 import { I18NextService } from "../../services";
6 import { Icon, Spinner } from "../common/icon";
7 import { PostListing } from "../post/post-listing";
8
9 interface VoteButtonsProps {
10   postListing: PostListing;
11   enableDownvotes?: boolean;
12   upvoteLoading?: boolean;
13   downvoteLoading?: boolean;
14   handleUpvote: (i: PostListing) => void;
15   handleDownvote: (i: PostListing) => void;
16   counts: CommentAggregates | PostAggregates;
17   my_vote?: number;
18 }
19
20 interface VoteButtonsState {
21   upvoteLoading: boolean;
22   downvoteLoading: boolean;
23 }
24
25 export class VoteButtonsCompact extends Component<
26   VoteButtonsProps,
27   VoteButtonsState
28 > {
29   state: VoteButtonsState = {
30     upvoteLoading: false,
31     downvoteLoading: false,
32   };
33
34   constructor(props: any, context: any) {
35     super(props, context);
36   }
37
38   get pointsTippy(): string {
39     const points = I18NextService.i18n.t("number_of_points", {
40       count: Number(this.props.counts.score),
41       formattedCount: Number(this.props.counts.score),
42     });
43
44     const upvotes = I18NextService.i18n.t("number_of_upvotes", {
45       count: Number(this.props.counts.upvotes),
46       formattedCount: Number(this.props.counts.upvotes),
47     });
48
49     const downvotes = I18NextService.i18n.t("number_of_downvotes", {
50       count: Number(this.props.counts.downvotes),
51       formattedCount: Number(this.props.counts.downvotes),
52     });
53
54     return `${points} • ${upvotes} • ${downvotes}`;
55   }
56
57   get tippy() {
58     return showScores() ? { "data-tippy-content": this.pointsTippy } : {};
59   }
60
61   render() {
62     return (
63       <>
64         <div className="input-group input-group-sm w-auto">
65           <button
66             className={`btn btn-sm btn-animate btn-outline-primary rounded-start py-0 ${
67               this.props.my_vote === 1 ? "text-info" : "text-muted"
68             }`}
69             {...this.tippy}
70             onClick={linkEvent(this.props.postListing, this.props.handleUpvote)}
71             aria-label={I18NextService.i18n.t("upvote")}
72             aria-pressed={this.props.my_vote === 1}
73           >
74             {this.state.upvoteLoading ? (
75               <Spinner />
76             ) : (
77               <>
78                 <Icon icon="arrow-up1" classes="icon-inline small" />
79                 {showScores() && (
80                   <span className="ms-2">
81                     {numToSI(this.props.counts.upvotes)}
82                   </span>
83                 )}
84               </>
85             )}
86           </button>
87           <span className="input-group-text small py-0">
88             {numToSI(this.props.counts.score)}
89           </span>
90           {this.props.enableDownvotes && (
91             <button
92               className={`btn btn-sm btn-animate btn-outline-primary rounded-end py-0 ${
93                 this.props.my_vote === -1 ? "text-danger" : "text-muted"
94               }`}
95               onClick={linkEvent(
96                 this.props.postListing,
97                 this.props.handleDownvote
98               )}
99               {...this.tippy}
100               aria-label={I18NextService.i18n.t("downvote")}
101               aria-pressed={this.props.my_vote === -1}
102             >
103               {this.state.downvoteLoading ? (
104                 <Spinner />
105               ) : (
106                 <>
107                   <Icon icon="arrow-down1" classes="icon-inline small" />
108                   {showScores() && (
109                     <span className="ms-2">
110                       {numToSI(this.props.counts.downvotes)}
111                     </span>
112                   )}
113                 </>
114               )}
115             </button>
116           )}
117         </div>
118       </>
119     );
120   }
121 }
122
123 export class VoteButtons extends Component<VotesProps, VotesState> {
124   state: VotesState = {
125     upvoteLoading: false,
126     downvoteLoading: false,
127   };
128
129   constructor(props: any, context: any) {
130     super(props, context);
131   }
132
133   get pointsTippy(): string {
134     const points = I18NextService.i18n.t("number_of_points", {
135       count: Number(this.props.counts.score),
136       formattedCount: Number(this.props.counts.score),
137     });
138
139     const upvotes = I18NextService.i18n.t("number_of_upvotes", {
140       count: Number(this.props.counts.upvotes),
141       formattedCount: Number(this.props.counts.upvotes),
142     });
143
144     const downvotes = I18NextService.i18n.t("number_of_downvotes", {
145       count: Number(this.props.counts.downvotes),
146       formattedCount: Number(this.props.counts.downvotes),
147     });
148
149     return `${points} • ${upvotes} • ${downvotes}`;
150   }
151
152   get tippy() {
153     return showScores() ? { "data-tippy-content": this.pointsTippy } : {};
154   }
155
156   render() {
157     return (
158       <div className={`vote-bar col-1 pe-0 small text-center`}>
159         <button
160           className={`btn-animate btn btn-link p-0 ${
161             this.props.my_vote == 1 ? "text-info" : "text-muted"
162           }`}
163           onClick={linkEvent(this.props.postListing, this.props.handleUpvote)}
164           data-tippy-content={I18NextService.i18n.t("upvote")}
165           aria-label={I18NextService.i18n.t("upvote")}
166           aria-pressed={this.props.my_vote === 1}
167         >
168           {this.state.upvoteLoading ? (
169             <Spinner />
170           ) : (
171             <Icon icon="arrow-up1" classes="upvote" />
172           )}
173         </button>
174         {showScores() ? (
175           <div
176             className={`unselectable pointer text-muted px-1 post-score`}
177             data-tippy-content={this.pointsTippy}
178           >
179             {numToSI(this.props.counts.score)}
180           </div>
181         ) : (
182           <div className="p-1"></div>
183         )}
184         {this.props.enableDownvotes && (
185           <button
186             className={`btn-animate btn btn-link p-0 ${
187               this.props.my_vote == -1 ? "text-danger" : "text-muted"
188             }`}
189             onClick={linkEvent(
190               this.props.postListing,
191               this.props.handleDownvote
192             )}
193             data-tippy-content={I18NextService.i18n.t("downvote")}
194             aria-label={I18NextService.i18n.t("downvote")}
195             aria-pressed={this.props.my_vote === -1}
196           >
197             {this.state.downvoteLoading ? (
198               <Spinner />
199             ) : (
200               <Icon icon="arrow-down1" classes="downvote" />
201             )}
202           </button>
203         )}
204       </div>
205     );
206   }
207 }