restyle PostCard

This commit is contained in:
SrGooglo 2023-02-24 14:33:00 +00:00
parent 5f03591d75
commit f3ccb4464f
13 changed files with 429 additions and 550 deletions

View File

@ -1,5 +1,5 @@
import React from "react"
import { Dropdown, Button } from "antd"
import { Icons } from "components/Icons"
import SaveButton from "./saveButton"
@ -7,28 +7,68 @@ import LikeButton from "./likeButton"
import "./index.less"
const MoreActionsItems = [
{
key: "repost",
label: <>
<Icons.Repeat />
<span>Repost</span>
</>,
},
{
key: "share",
label: <>
<Icons.Share />
<span>Share</span>
</>,
},
{
type: "divider",
},
{
key: "report",
label: <>
<Icons.AlertTriangle />
<span>Report</span>
</>,
},
]
export default (props) => {
return <div className="post_actionsWrapper">
return <div className="post_actions_wrapper">
<div className="actions">
<div className="action" id="likes">
<div className="icon">
<LikeButton defaultLiked={props.defaultLiked} onClick={props.onClickLike} />
</div>
<LikeButton
defaultLiked={props.defaultLiked}
onClick={props.onClickLike}
count={props.likesCount}
/>
</div>
<div className="action" id="save">
<SaveButton
defaultActive={props.defaultSaved}
onClick={props.onClickSave}
/>
</div>
<div className="action" id="comments">
<Button
type="ghost"
shape="circle"
onClick={props.onClickComments}
icon={<Icons.MessageCircle />}
/>
</div>
<div className="action" id="more">
<Dropdown
menu={{
items: MoreActionsItems
}}
trigger={["click"]}
>
<div className="icon">
<SaveButton defaultActive={props.defaultSaved} onClick={props.onClickSave} />
</div>
</div>
<div className="action" id="share">
<div className="icon">
<Icons.Share2 onClick={props.onClickShare} />
</div>
</div>
<div className="action" id="open" onClick={props.onClickOpen}>
<div className="icon">
<Icons.MdOutlineOpenInNew className="icon" />
<Icons.MoreHorizontal />
</div>
</Dropdown>
</div>
</div>
</div>

View File

@ -1,93 +1,47 @@
.post_actionsIndicator {
.post_actions_wrapper {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
justify-content: flex-start;
width: 10vw;
padding: 2px;
margin: auto;
border-radius: 8px 8px 0 0;
background-color: var(--background-color-primary);
color: var(--background-color-contrast);
font-size: 18px;
transition: all 0.2s ease-in-out;
svg {
margin: 0 !important;
}
}
.post_actionsWrapper {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
position: absolute;
bottom: 0;
left: 0;
opacity: 0;
position: relative;
width: 100%;
height: 40px;
margin-top: 15px;
padding: 10px;
padding: 20px;
border-radius: 8px;
transition: all 0.2s ease-in-out;
background-color: var(--background-color-primary);
.actions {
display: inline-flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 80%;
transition: all 0.2s ease-in-out;
color: var(--background-color-contrast);
width: 100%;
.action {
display: inline-flex;
flex-direction: column;
flex-direction: row;
transition: all 0.2s ease-in-out;
align-items: center;
justify-content: center;
.icon {
cursor: pointer;
transition: all 0.2s ease-in-out;
transition: all 150ms ease-in-out;
svg {
transition: all 0.2s ease-in-out;
}
margin-right: 20px;
&:last-child {
margin-right: 0;
}
.value {
position: absolute;
bottom: 0;
font-size: 14px;
font-family: "DM Mono", monospace;
transform: translate(0, 50%);
transition: all 0.2s ease-in-out;
}
}
.action:hover {
&:hover {
.icon {
svg {
color: var(--primaryColor) !important;
color: var(--colorPrimary) !important;
}
}
}
}
@ -95,22 +49,5 @@
svg {
margin: 0 !important;
}
>div {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
border-radius: 360px;
width: 55px;
height: 55px;
font-size: 20px;
padding: 2px;
background-color: var(--background-color-primary);
transform: translate(0, -15px);
}
}
}

View File

@ -1,5 +1,6 @@
import React from "react"
import classnames from "classnames"
import CountUp from "react-countup"
import "./index.less"
@ -22,22 +23,22 @@ export default (props) => {
setLiked(to)
}
return <button
className={classnames("likeButton", { ["clicked"]: liked })}
return <div
className={
classnames(
"like_btn_wrapper",
{
["liked"]: liked,
["clicked"]: clicked
}
)
}
onClick={handleClick}
>
<div
className={classnames(
"ripple",
{ ["clicked"]: clicked }
)}
></div>
<button className="like_btn">
<div className="ripple"></div>
<svg
className={classnames(
"heart",
{ ["liked"]: liked },
{ ["clicked"]: clicked },
)}
className="heart"
width="24"
height="24"
viewBox="0 0 24 24"
@ -45,4 +46,14 @@ export default (props) => {
<path d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"></path>
</svg>
</button>
<CountUp
start={props.count}
separator="."
end={props.count}
startOnMount={false}
duration={3}
className="count"
useEasing={true}
/>
</div>
}

View File

@ -2,73 +2,48 @@
@likeAnimationDuration : .5s;
@likeAnimationEasing : cubic-bezier(.7, 0, .3, 1);
.likeButton {
.like_btn_wrapper {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.ripple,
.ripple:before,
.ripple:after {
position : relative;
box-sizing: border-box;
font-size: 1rem;
.count {
display: flex;
flex-direction: row;
align-items: center;
margin-left: 7px;
font-size: 0.8rem;
color: var(--text-color);
}
font-size : 40px;
border : none;
border-radius: 50%;
width : 1em;
height : 1em;
padding : 0;
margin : 0;
outline : none;
z-index : 2;
transition : transform @likeAnimationDuration @likeAnimationEasing;
cursor : pointer;
//padding: 10px;
//border-radius: 8px;
background-color: transparent;
&:before {
z-index : -1;
content : '';
position : absolute;
top : 0;
left : 0;
width : 100%;
height : 100%;
border-radius: inherit;
transition : inherit;
}
&:after {
content : '';
position : absolute;
top : 0;
left : 0;
width : 100%;
height : 100%;
border-radius: inherit;
z-index : -1;
}
.heart {
position: relative;
>path {
stroke-width: 2;
transition : fill @likeAnimationDuration @likeAnimationEasing;
stroke : currentColor;
fill : transparent;
}
transition: all @likeAnimationDuration @likeAnimationEasing;
&.liked {
.like_btn {
.heart {
>path {
stroke: var(--primaryColor);
fill : var(--primaryColor);
stroke: var(--colorPrimary);
fill: var(--colorPrimary);
}
filter: drop-shadow(0px 0px 2px var(--colorPrimary));
}
}
//outline: 1px solid var(--colorPrimary);
}
&.clicked {
.like_btn {
.heart {
animation: heart-bounce @likeAnimationDuration @likeAnimationEasing;
@keyframes heart-bounce {
@ -84,15 +59,89 @@
}
}
.ripple {
&:before {
animation: ripple-out @likeAnimationDuration @likeAnimationEasing;
}
}
}
}
.like_btn {
display: flex;
align-items: center;
justify-content: center;
.ripple,
.ripple:before,
.ripple:after {
position: relative;
box-sizing: border-box;
}
border: none;
border-radius: 50%;
width: 1rem;
height: 1rem;
padding: 0;
margin: 0;
z-index: 2;
transition: transform @likeAnimationDuration @likeAnimationEasing;
background-color: transparent;
&:before {
z-index: -1;
content: '';
position: absolute;
top: 0;
left: 0;
width: 1rem;
height: 1rem;
border-radius: inherit;
transition: inherit;
}
&:after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 1rem;
height: 1rem;
border-radius: inherit;
z-index: -1;
}
.heart {
position: relative;
cursor: pointer;
>path {
stroke-width: 2;
transition: fill @likeAnimationDuration @likeAnimationEasing;
stroke: currentColor;
fill: transparent;
}
animation: none;
}
.ripple {
position: absolute;
height : 1em;
width : 1em;
height: 1rem;
width: 1rem;
border-radius: 50%;
overflow: hidden;
z-index: 1;
&:before {
@ -102,18 +151,12 @@
left: 0;
width: 100%;
height: 100%;
border : .4em solid var(--primaryColor);
border: .4em solid var(--colorPrimary);
border-radius: inherit;
transform: scale(0);
}
&.clicked {
&:before {
animation: ripple-out @likeAnimationDuration @likeAnimationEasing;
}
}
}
}
@keyframes ripple-out {

View File

@ -20,9 +20,9 @@ export default (props) => {
className={classnames("saveButton", {
["active"]: saved
})}
type="ghost"
shape="circle"
onClick={onClick}
icon={saved ? <Icons.MdBookmark /> : <Icons.MdBookmarkBorder />}
size="large"
/>
}

View File

@ -1,22 +1,21 @@
.saveButton {
border: 0 !important;
transition: all 150ms ease-in-out;
background: transparent !important;
background-color: transparent!important;
border: 0!important;
box-shadow: none!important;
&.active {
color: var(--primaryColor);
color: var(--colorPrimary);
svg {
color: var(--primaryColor);
color: var(--colorPrimary);
}
}
svg {
width: 20px;
height: 20px;
width: 1rem;
height: 1rem;
transition: all 150ms ease-in-out;
}

View File

@ -3,7 +3,6 @@ import { Skeleton } from "antd"
import { Carousel } from "react-responsive-carousel"
import { ImageViewer } from "components"
import Plyr from "plyr-react"
import ModalImage from "react-modal-image"
import ContentFailed from "../contentFailed"
@ -43,7 +42,7 @@ const Attachment = React.memo((props) => {
e.preventDefault()
e.stopPropagation()
app.openFullImageViewer(url)
app.controls.openFullImageViewer(url)
}
}

View File

@ -88,6 +88,12 @@
}
.attachment {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
@ -136,7 +142,7 @@
.plyr__control {
&:hover {
background-color: var(--primaryColor);
background-color: var(--colorPrimary);
}
svg {
@ -149,7 +155,7 @@
.plyr__progress,
.plyr__volume {
input {
color: var(--primaryColor);
color: var(--colorPrimary);
}
}
}

View File

@ -66,13 +66,13 @@
.message {
width: 100%;
font-size: 14px;
font-family: "Poppins", sans-serif;
font-size: 0.9rem;
font-weight: 400;
color: var(--background-color-contrast);
word-break: break-all;
user-select: text;
color: var(--background-color-contrast)
}
>div {

View File

@ -1,5 +1,4 @@
import React from "react"
import classnames from "classnames"
import { DateTime } from "luxon"
import { Image } from "components"
@ -11,13 +10,13 @@ export default (props) => {
const [timeAgo, setTimeAgo] = React.useState(0)
const goToProfile = () => {
window.app.goToAccount(props.postData.user?.username)
app.navigation.goToAccount(props.postData.user?.username)
}
const updateTimeAgo = () => {
let createdAt = props.postData.timestamp ?? props.postData.created_at ?? ""
const timeAgo = DateTime.fromISO(createdAt).toRelative()
const timeAgo = DateTime.fromISO(createdAt, { locale: app.cores.settings.get("language") }).toRelative()
setTimeAgo(timeAgo)
}
@ -43,30 +42,14 @@ export default (props) => {
/>
</div>
<div className="info">
<div>
<h1 onClick={goToProfile}>
{props.postData.user?.fullName ?? `@${props.postData.user?.username}`}
{props.postData.user?.verified && <Icons.verifiedBadge />}
</h1>
</div>
<div>
<span className="timeago">
{timeAgo}
</div>
</div>
</div>
<div className="statistics">
<div className="item">
<Icons.Heart className={classnames("icon", { ["filled"]: props.isLiked })} />
<div className="value">
{props.likes}
</div>
</div>
<div className="item">
<Icons.MessageSquare />
<div className="value">
{props.comments}
</div>
</span>
</div>
</div>
</div>

View File

@ -15,7 +15,7 @@
}
svg {
fill: var(--appColor);
fill: var(--app-color);
margin-left: 6px;
}
@ -35,22 +35,29 @@
.info {
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: start;
text-align: start;
align-items: flex-start;
width: fit-content;
color: var(--background-color-contrast);
h1 {
color: var(--background-color-contrast);
margin: 0;
font-family: "DM Mono", monospace;
margin: 0 0 4px 0;
align-self: start;
font-weight: 500;
font-size: 1rem;
cursor: pointer;
color: var(--background-color-contrast);
}
.timeago {
font-weight: 400;
font-size: 0.7rem;
color: rgb(var(--bg_color_4));
}
>div {
@ -58,35 +65,4 @@
}
}
}
.statistics {
display: inline-flex;
flex-direction: column;
font-size: 16px;
color: var(--background-color-contrast);
height: fit-content;
.item {
display: inline-flex;
align-items: center;
justify-content: flex-end;
height: fit-content;
margin-left: 20px;
margin-bottom: 5px;
.icon {
&.filled {
color: var(--primaryColor);
fill: var(--primaryColor);
}
}
.value {
font-family: "DM Mono", monospace;
font-size: 14px;
}
}
}
}

View File

@ -2,6 +2,7 @@ import React from "react"
import * as antd from "antd"
import classnames from "classnames"
import { CommentsCard } from "components"
import { Icons } from "components/Icons"
import PostHeader from "./components/header"
@ -11,184 +12,116 @@ import PostAttachments from "./components/attachments"
import "./index.less"
export default ({
expansibleActions = window.app.settings.get("postCard_expansible_actions"),
autoCarrousel = window.app.settings.get("postCard_carrusel_auto"),
data = {},
events = {},
fullmode
}) => {
const isSelf = app.permissions.checkUserIdIsSelf(data.user_id)
export default class PostCard extends React.Component {
state = {
loading: true,
data: this.props.data ?? {},
const [loading, setLoading] = React.useState(true)
countLikes: this.props.data.countLikes ?? 0,
countComments: this.props.data.countComments ?? 0,
const [likes, setLikes] = React.useState(data.likes ?? [])
const [comments, setComments] = React.useState(data.comments ?? [])
hasLiked: this.props.data.isLiked ?? false,
hasSaved: this.props.data.isSaved ?? false,
const [hasLiked, setHasLiked] = React.useState(false)
const [hasSaved, setHasSaved] = React.useState(false)
open: this.props.defaultOpened ?? false,
}
const onClickDelete = async () => {
if (typeof events.onClickDelete !== "function") {
onClickDelete = async () => {
if (typeof this.props.events.onClickDelete !== "function") {
console.warn("onClickDelete event is not a function")
return
}
return await events.onClickDelete(data)
return await this.props.events.onClickDelete(this.state.data)
}
const onClickLike = async () => {
if (typeof events.onClickLike !== "function") {
onClickLike = async () => {
if (typeof this.props.events.onClickLike !== "function") {
console.warn("onClickLike event is not a function")
return
}
return await events.onClickLike(data)
return await this.props.events.onClickLike(this.state.data)
}
const onClickSave = async () => {
if (typeof events.onClickSave !== "function") {
onClickSave = async () => {
if (typeof this.props.events.onClickSave !== "function") {
console.warn("onClickSave event is not a function")
return
}
return await events.onClickSave(data)
return await this.props.events.onClickSave(this.state.data)
}
const onClickOpen = async () => {
if (typeof events.onClickOpen !== "function") {
console.warn("onClickOpen event is not a function, performing default action")
return window.app.goToPost(data._id)
}
return await events.onClickOpen(data)
}
const onClickEdit = async () => {
if (typeof events.onClickEdit !== "function") {
onClickEdit = async () => {
if (typeof this.props.events.onClickEdit !== "function") {
console.warn("onClickEdit event is not a function")
return
}
return await events.onClickEdit(data)
return await this.props.events.onClickEdit(this.state.data)
}
const onDataUpdate = (data) => {
console.log("onDataUpdate", data)
setLikes(data.likes)
setComments(data.comments)
onDoubleClick = async () => {
this.handleOpen()
}
const onDoubleClick = () => {
if (typeof events.onDoubleClick !== "function") {
console.warn("onDoubleClick event is not a function")
return
onClickComments = async () => {
this.handleOpen()
}
return events.onDoubleClick(data)
handleOpen = (to) => {
if (typeof to === "undefined") {
to = !this.state.open
}
React.useEffect(() => {
if (fullmode) {
app.eventBus.emit("style.compactMode", true)
if (typeof this.props.events?.onToogleOpen === "function") {
this.props.events?.onToogleOpen(to, this.state.data)
}
app.eventBus.on(`post.${data._id}.delete`, onClickDelete)
app.eventBus.on(`post.${data._id}.update`, onClickEdit)
// first listen to post changes
window.app.api.namespaces["main"].listenEvent(`post.dataUpdate.${data._id}`, onDataUpdate)
// then load
setLoading(false)
return () => {
if (fullmode) {
app.eventBus.emit("style.compactMode", false)
}
app.eventBus.off(`post.${data._id}.delete`, onClickDelete)
app.eventBus.off(`post.${data._id}.update`, onClickEdit)
// remove the listener
window.app.api.namespaces["main"].unlistenEvent(`post.dataUpdate.${data._id}`, onDataUpdate)
}
}, [])
React.useEffect(() => {
if (!app.userData) {
return
}
// check if the post has liked by you
const hasLiked = likes.includes(app.userData._id)
const hasSaved = data.isSaved
setHasLiked(hasLiked)
setHasSaved(hasSaved)
this.setState({
open: to,
})
if (loading) {
return <antd.Skeleton active />
//app.controls.openPostViewer(this.state.data)
}
try {
return <div
key={data.key ?? data._id}
id={data._id}
className={classnames(
"postCard",
data.type,
{ ["liked"]: hasLiked },
{ ["noHide"]: window.isMobile || !expansibleActions },
{ ["fullmode"]: fullmode },
)}
context-menu={"postCard-context"}
user-id={data.user_id}
self-post={isSelf.toString()}
>
<div className="wrapper">
<PostHeader
postData={data}
isLiked={hasLiked}
likes={likes.length}
comments={comments.length}
fullmode={fullmode}
onDoubleClick={onDoubleClick}
/>
<PostContent
data={data}
autoCarrousel={autoCarrousel}
fullmode={fullmode}
onDoubleClick={onDoubleClick}
nsfw={data.flags && data.flags.includes("nsfw")}
>
{data.attachments && data.attachments.length > 0 && <PostAttachments
attachments={data.attachments}
/>}
</PostContent>
</div>
{!fullmode &&
<div className="post_actionsIndicator">
<Icons.MoreHorizontal />
</div>
onLikesUpdate = (data) => {
if (data.to) {
this.setState({
countLikes: this.state.countLikes + 1,
})
} else {
this.setState({
countLikes: this.state.countLikes - 1,
})
}
{!fullmode &&
<PostActions
defaultLiked={hasLiked}
defaultSaved={hasSaved}
onClickLike={onClickLike}
onClickSave={onClickSave}
onClickOpen={onClickOpen}
actions={{
delete: onClickDelete,
}}
fullmode={fullmode}
/>
}
</div>
} catch (error) {
componentDidMount = async () => {
app.eventBus.on(`post.${this.state.data._id}.delete`, this.onClickDelete)
app.eventBus.on(`post.${this.state.data._id}.update`, this.onClickEdit)
// first listen to post changes
//window.app.cores.api.namespaces["main"].listenEvent(`post.dataUpdate.${data._id}`, onDataUpdate)
window.app.cores.api.namespaces["main"].listenEvent(`post.${this.state.data._id}.likes.update`, this.onLikesUpdate)
this.setState({
isSelf: app.cores.permissions.checkUserIdIsSelf(this.state.data.user_id),
loading: false,
})
}
componentWillUnmount = () => {
app.eventBus.off(`post.${this.state.data._id}.delete`, this.onClickDelete)
app.eventBus.off(`post.${this.state.data._id}.update`, this.onClickEdit)
// remove the listener
//window.app.cores.api.namespaces["main"].unlistenEvent(`post.dataUpdate.${data._id}`, onDataUpdate)
window.app.cores.api.namespaces["main"].listenEvent(`post.${this.state.data._id}.likes.update`, this.onLikesUpdate)
}
componentDidCatch = (error, info) => {
console.error(error)
return <div className="postCard error">
@ -201,4 +134,63 @@ export default ({
</h1>
</div>
}
render = () => {
if (this.state.loading) {
return <div
key={this.state.data.key ?? this.state.data._id}
id={this.state.data._id}
className="postCard"
>
<antd.Skeleton active avatar />
</div>
}
return <div
key={this.state.data.key ?? this.state.data._id}
id={this.state.data._id}
className={classnames(
"postCard",
{
["open"]: this.state.open,
}
)}
context-menu={"postCard-context"}
user-id={this.state.data.user_id}
self-post={this.state.isSelf.toString()}
>
<div className="wrapper">
<PostHeader
postData={this.state.data}
isLiked={this.state.hasLiked}
onDoubleClick={this.onDoubleClick}
/>
<PostContent
data={this.state.data}
nsfw={this.state.data.flags && this.state.data.flags.includes("nsfw")}
onDoubleClick={this.onDoubleClick}
>
{this.state.data.attachments && this.state.data.attachments.length > 0 && <PostAttachments
attachments={this.state.data.attachments}
/>}
</PostContent>
</div>
<PostActions
likesCount={this.state.countLikes}
commentsCount={this.state.countComments}
defaultLiked={this.state.hasLiked}
defaultSaved={this.state.hasSaved}
onClickLike={this.onClickLike}
onClickSave={this.onClickSave}
onClickComments={this.onClickComments}
actions={{
delete: this.onClickDelete,
}}
/>
{
this.state.open && <CommentsCard post_id={this.state.data._id} />
}
</div>
}
}

View File

@ -5,137 +5,23 @@
width: 100%;
max-width: 600px;
filter: drop-shadow(3px 3px 2px var(--shadow-color));
background-color: var(--background-color-accent);
border-radius: 8px;
transition: all 0.2s ease-in-out;
outline-width: 1px;
outline-style: solid;
outline-color: transparent;
&.playlist {
.wrapper {
.post_content {
flex-direction: row;
background-color: var(--background-color-primary);
padding: 20px;
.playlistCover {
width: 200px;
height: 200px;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
border-radius: 8px;
margin-right: 25px;
}
.playlistTitle {
h1 {
font-size: 1.5rem;
font-family: "Space Grotesk", sans-serif;
margin: 0;
}
h3 {
font-size: 0.9rem;
// make italic
font-style: italic;
}
.actions {
width: 100%;
margin-top: 20px;
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
}
}
}
}
&.liked {
filter: drop-shadow(0px 0px 2px var(--primaryColor));
outline-color: var(--primaryColor);
}
&.noHide {
.wrapper {
margin-bottom: 25px;
}
.post_actionsWrapper {
opacity: 1;
}
}
&.fullmode {
max-width: none;
height: 100%;
background-color: transparent;
filter: none!important;
outline: none!important;
.post_actionsIndicator {
opacity: 0;
}
.post_actionsWrapper {
opacity: 1;
}
.wrapper {
height: 100%;
.post_content {
height: 100%;
.post_attachments {
.carousel-root {
.carousel {
height: 100%;
min-height: 70vh;
.addition {
height: 100%;
min-height: 70vh;
.plyr {
height: 100%;
}
img,
video {
height: 100%;
object-fit: contain;
}
}
}
}
}
}
}
}
.wrapper {
display: inline-flex;
flex-direction: column;
align-items: center;
width: 100%;
padding: 17px;
&.open {
height: 100%;
}
.wrapper {
display: inline-flex;
flex-direction: column;
align-items: center;
width: 100%;
transition: all 0.2s ease-in-out;
>div {
@ -143,13 +29,20 @@
}
}
&:hover {
.wrapper {
margin-bottom: 25px;
border-bottom: 2px solid var(--border-color);
padding-bottom: 10px;
&:first-child {
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.post_actionsWrapper {
opacity: 1;
}
&:last-child {
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
border-bottom: none;
padding-bottom: 0;
}
}