From 1ed9438972ad67ca57e52b0bf8a07f2f044066f9 Mon Sep 17 00:00:00 2001
From: freeplay <freeplay@duck.com>
Date: Thu, 13 Jul 2023 19:26:44 -0400
Subject: [PATCH] style: user card design

---
 packages/client/src/components/MkUserInfo.vue | 320 +++++++++++++-----
 1 file changed, 244 insertions(+), 76 deletions(-)

diff --git a/packages/client/src/components/MkUserInfo.vue b/packages/client/src/components/MkUserInfo.vue
index 9c2763e53..9ff5f8b6c 100644
--- a/packages/client/src/components/MkUserInfo.vue
+++ b/packages/client/src/components/MkUserInfo.vue
@@ -1,151 +1,319 @@
 <template>
-	<div class="_panel vjnjpkug">
+	<article class="_panel user-card">
 		<div
 			class="banner"
 			:style="
 				user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''
 			"
+			:class="{ detailed }"
 		></div>
-		<MkAvatar
-			class="avatar"
-			:user="user"
-			:disable-preview="true"
-			:show-indicator="true"
-		/>
-		<div class="title">
+		<h3 class="title">
+			<MkAvatar
+				class="avatar"
+				:user="user"
+				:disable-preview="true"
+				:show-indicator="true"
+			/>
 			<MkA class="name" :to="userPage(user)"
-				><MkUserName :user="user" :nowrap="false"
+				><MkUserName :user="user" :nowrap="true"
 			/></MkA>
 			<p class="username"><MkAcct :user="user" /></p>
-		</div>
-		<div class="description">
-			<div v-if="user.description" class="mfm">
-				<Mfm
-					:text="user.description"
-					:author="user"
-					:i="$i"
-					:custom-emojis="user.emojis"
-				/>
-			</div>
+		</h3>
+		<div 
+			class="description"
+			:class="{ collapsed: isLong && collapsed, truncate: !detailed }"
+		>
+			<Mfm
+				v-if="user.description"
+				class="mfm"
+				:text="user.description"
+				:author="user"
+				:i="$i"
+				:custom-emojis="user.emojis"
+			/>
 			<span v-else style="opacity: 0.7">{{
 				i18n.ts.noAccountDescription
 			}}</span>
 		</div>
+		<XShowMoreButton
+			v-if="isLong"
+			v-model="collapsed"
+		></XShowMoreButton>
+		<div v-if="user.fields.length > 0 && detailed" class="fields">
+			<dl
+				v-for="(field, i) in user.fields"
+				:key="i"
+				class="field"
+			>
+				<dt class="name">
+					<Mfm
+						:text="field.name"
+						:plain="true"
+						:custom-emojis="user.emojis"
+						:colored="false"
+					/>
+				</dt>
+				<dd class="value">
+					<Mfm
+						:text="field.value"
+						:author="user"
+						:i="$i"
+						:custom-emojis="user.emojis"
+						:colored="false"
+					/>
+				</dd>
+			</dl>
+		</div>
 		<div class="status">
-			<div>
-				<p>{{ i18n.ts.notes }}</p>
+			<p>
 				<MkNumber :value="user.notesCount" />
-			</div>
-			<div>
-				<p>{{ i18n.ts.following }}</p>
+				{{ i18n.ts.notes }}
+			</p>
+			<p>
 				<MkNumber :value="user.followingCount" />
-			</div>
-			<div>
-				<p>{{ i18n.ts.followers }}</p>
+				{{ i18n.ts.following }}
+			</p>
+			<p>
 				<MkNumber :value="user.followersCount" />
-			</div>
+				{{ i18n.ts.followers }}
+			</p>
 		</div>
-		<div class="koudoku-button">
-			<MkFollowButton v-if="$i && user.id != $i.id" :user="user" />
+		<div class="buttons">
+			<slot>
+				<MkFollowButton v-if="$i && user.id != $i.id" :user="user" />
+			</slot>
 		</div>
-	</div>
+	</article>
 </template>
 
 <script lang="ts" setup>
 import * as misskey from "calckey-js";
 import MkFollowButton from "@/components/MkFollowButton.vue";
+import XShowMoreButton from "@/components/MkShowMoreButton.vue";
 import MkNumber from "@/components/MkNumber.vue";
 import { userPage } from "@/filters/user";
 import { i18n } from "@/i18n";
 
-defineProps<{
+const props = defineProps<{
 	user: misskey.entities.UserDetailed;
+	detailed?: boolean;
 }>();
+
+let isLong = $ref(
+	props.detailed &&
+	(
+		props.user.description.split("\n").length > 9 ||
+		props.user.description.length > 400
+	)
+);
+let collapsed = $ref(isLong);
 </script>
 
 <style lang="scss" scoped>
-.vjnjpkug {
+.user-card {
 	position: relative;
+	display: flex;
+	flex-direction: column;
 
-	> .banner {
-		height: 84px;
+	.banner {
+		height: 94px;
 		background-color: rgba(0, 0, 0, 0.1);
 		background-size: cover;
 		background-position: center;
+		margin-bottom: calc(0px - var(--radius));
+		&::before {
+			content: "";
+			position: absolute;
+			inset: 0;
+			z-index: 2;
+			height: inherit;
+			background: linear-gradient(
+				-125deg,
+				rgba(0, 0, 0, 0.7),
+				transparent,
+				transparent
+			);
+		}
+		&.detailed::after {
+			content: "";
+			background-image: var(--blur, inherit);
+			position: fixed;
+			inset: 0;
+			background-size: cover;
+			background-position: center;
+			pointer-events: none;
+			opacity: 0.1;
+			filter: var(--blur, blur(10px));
+			z-index: 5;
+		}
 	}
 
-	> .avatar {
+	
+	.title {
+		position: relative;
 		display: block;
-		position: absolute;
-		top: 62px;
-		left: 13px;
-		z-index: 2;
-		width: 58px;
-		height: 58px;
-		border: solid 4px var(--panel);
-	}
-
-	> .title {
-		display: block;
-		padding: 10px 0 10px 88px;
-
-		> .name {
+		padding: 10px 10px 10px 84px;
+		margin: 0;
+		font-size: 1em;
+		border-radius: var(--radius);
+		background: var(--panel);
+		line-height: 1;
+		z-index: 4;
+		
+		.avatar {
+			display: block;
+			position: absolute;
+			bottom: 6px;
+			left: 10px;
+			z-index: 2;
+			width: 58px;
+			height: 58px;
+			background: var(--panel);
+			border: solid 4px var(--panel);
+		}
+		.name {
 			display: inline-block;
 			margin: 0;
 			font-weight: bold;
 			line-height: 16px;
-			word-break: break-all;
+			overflow: hidden;
+			text-overflow: ellipsis;
+			height: 1.5em;
+			margin-block: -0.5em;
+			padding-block: 0.5em 0.25em;
+			max-width: 100%;
 		}
 
-		> .username {
+		.username {
 			display: block;
 			margin: 0;
 			line-height: 16px;
 			font-size: 0.8em;
 			color: var(--fg);
+			font-weight: 500;
 			opacity: 0.7;
+			line-height: 1;
 		}
 	}
 
-	> .description {
-		padding: 16px;
+	.description {
+		overflow: hidden;
+		padding-inline: 16px;
+		margin-bottom: 10px;
 		font-size: 0.8em;
-		border-top: solid 0.5px var(--divider);
-
-		> .mfm {
+		&.truncate {
 			display: -webkit-box;
-			-webkit-line-clamp: 3;
+			-webkit-line-clamp: 6;
 			-webkit-box-orient: vertical;
-			overflow: hidden;
+		}
+		&.collapsed {
+			position: relative;
+			max-height: calc(9em + 50px);
+			mask: linear-gradient(black calc(100% - 64px), transparent);
+			-webkit-mask: linear-gradient(
+				black calc(100% - 64px),
+				transparent
+			);
+		}
+		&.collapsed, &.truncate {
+			:deep(br) {
+				display: none; // collapse white spaces
+			}
 		}
 	}
-
-	> .status {
-		padding: 10px 16px;
-		border-top: solid 0.5px var(--divider);
-
-		> div {
+	:deep(.fade) {
+		position: relative;
+		display: block;
+		width: 100%;
+		margin-top: -4.5em;
+		z-index: 2;
+		> span {
 			display: inline-block;
-			width: 33%;
-
-			> p {
-				margin: 0;
-				font-size: 0.7em;
-				color: var(--fg);
-			}
-
+			background: var(--panel);
+			padding: 0.4em 1em;
+			font-size: 0.8em;
+			border-radius: 999px;
+			box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
+		}
+		&:hover {
 			> span {
-				font-size: 1em;
-				color: var(--accent);
+				background: var(--panelHighlight);
 			}
 		}
 	}
+	:deep(.showLess) {
+		width: 100%;
+		position: sticky;
+		bottom: var(--stickyBottom);
 
-	> .koudoku-button {
+		> span {
+			display: inline-block;
+			background: var(--panel);
+			padding: 6px 10px;
+			font-size: 0.8em;
+			border-radius: 999px;
+			box-shadow: 0 0 7px 7px var(--bg);
+		}
+	}
+	> .fields {
+		padding-inline: 16px;
+		font-size: 0.8em;
+		padding-block: 1em;
+		border-top: 1px solid var(--divider);
+
+		> .field {
+			display: flex;
+			padding: 0;
+			margin: 0;
+			align-items: center;
+
+			&:not(:last-child) {
+				margin-bottom: 8px;
+			}
+
+			:deep(span) {
+				white-space: nowrap !important;
+			}
+
+			> .name {
+				width: 30%;
+				overflow: hidden;
+				white-space: nowrap;
+				text-overflow: ellipsis;
+				font-weight: bold;
+				text-align: center;
+				padding-right: 10px;
+			}
+
+			> .value {
+				width: 70%;
+				overflow: hidden;
+				white-space: nowrap;
+				text-overflow: ellipsis;
+				margin: 0;
+			}
+		}
+	}
+	.status {
+		display: flex;
+		gap: 1em;
+		padding-inline: 16px;
+		font-size: 0.8em;
+		margin-top: auto;
+		border-top: 1px solid var(--divider);
+		> p > :deep(span) {
+			font-weight: 700;
+			color: var(--accent);
+		}
+	}
+
+	.buttons {
 		position: absolute;
 		top: 8px;
 		right: 8px;
 		margin-bottom: 1rem;
+		z-index: 3;
+		color: white;
 	}
 }
 </style>