BottomSheet: design update

This commit is contained in:
10G Meow 2025-10-19 14:05:29 +03:00
parent 47b4260761
commit afc6fb0a33
5 changed files with 48 additions and 171 deletions

View file

@ -13,20 +13,22 @@
</v-fade-transition> </v-fade-transition>
<div <div
class="bottom-sheet-content" class="bottom-sheet-content"
:data-state="isMove ? 'move' : state" :data-state="state"
ref="pan" ref="pan"
:style="{ top: `${isMove ? y : calcY()}px` }"
> >
<v-btn <v-container>
size="small" <v-row justify="end">
elevation="0" <v-col cols="1" class="text-right">
@click.stop="onBackgroundClick" <v-btn
class="bottom-sheet-close" size="small"
v-if="showCloseButton" elevation="0"
icon="cancel" @click.stop="onBackgroundClick"
> icon="close"
</v-btn> >
<div class="bottom-sheet-handle"><div class="bottom-sheet-handle-decoration" /></div> </v-btn>
</v-col>
</v-row>
</v-container>
<div ref="sheetContent" class="sheetContent"> <div ref="sheetContent" class="sheetContent">
<slot></slot> <slot></slot>
</div> </div>
@ -35,102 +37,13 @@
</template> </template>
<script> <script>
import Hammer from "hammerjs";
export default { export default {
props: {
showCloseButton: {
type: Boolean,
default: true,
},
openY: {
type: Number,
default: 0.1,
},
halfY: {
type: Number,
default: 0.5,
},
defaultState: {
type: String,
default: "closed",
},
},
data() { data() {
return { return {
mc: null, state: "closed"
y: 0,
startY: 0,
startYCoord: 0,
isMove: false,
state: this.defaultState,
rect: {},
}; };
}, },
mounted() {
window.onresize = () => {
this.rect = this.$refs.sheet.getBoundingClientRect();
};
this.rect = this.$refs.sheet.getBoundingClientRect();
this.mc = new Hammer(this.$refs.pan);
this.mc.get("pan").set({ direction: Hammer.DIRECTION_ALL });
const self = this;
this.mc.on("panup pandown", (evt) => {
self.y = self.startYCoord - (self.startY - evt.center.y);
});
this.mc.on("panstart", (evt) => {
self.startY = evt.center.y;
self.startYCoord = this.calcY();
self.isMove = true;
});
this.mc.on("pancancel", (ignoredEvt) => {
self.y = self.startYCoord;
self.isMove = false;
});
this.mc.on("panend", (evt) => {
self.isMove = false;
switch (self.state) {
case "small":
if (self.startY - evt.center.y > 120) {
self.state = "open";
}
if (self.startY - evt.center.y < -50) {
self.state = "closed";
}
break;
case "open":
if (self.startY - evt.center.y < -120) {
self.state = "small";
}
break;
}
});
},
beforeUnmount() {
this.mc.destroy();
window.onresize = null;
},
methods: { methods: {
calcY() {
switch (this.state) {
case "closed":
return this.rect.height;
case "open":
return this.rect.height * this.openY;
case "small":
return this.rect.height * this.halfY;
default:
return this.y;
}
},
open() { open() {
this.setState("small"); this.setState("small");
}, },
@ -167,11 +80,13 @@ export default {
.sheetContent { .sheetContent {
position:absolute; position:absolute;
top:20px;left:0; top: 60px;
padding: 0 20px 20px 20px;
left:0;
right:0; right:0;
bottom:0; bottom:0;
overflow-y:auto; overflow-y:auto;
padding:20px; border-top: 1px solid #E1E1E1;
} }
.bottom-sheet { .bottom-sheet {
@ -194,23 +109,9 @@ export default {
background-color: rgba(black, 0.4); background-color: rgba(black, 0.4);
} }
.bottom-sheet-handle {
height: 20px;
background-color: white;
position: relative;
.bottom-sheet-handle-decoration {
background-color: #cccccc;
height: 2px;
position: absolute;
top: 50%;
left: 30%;
right: 30%;
}
}
.bottom-sheet-content { .bottom-sheet-content {
position: absolute; position: absolute;
top: 0px; top: 100px;
bottom: 0px; bottom: 0px;
left: 0; left: 0;
right: 0; right: 0;
@ -218,11 +119,8 @@ export default {
background-color: white; background-color: white;
overflow: hidden; overflow: hidden;
.bottom-sheet-close { &[data-state="small"], &[data-state="open"], &[data-state="closed"] {
position: absolute; transition: top 0.3s ease-out;
right: 4px;
top: 4px;
z-index: 1;
} }
@media #{map.get($display-breakpoints, 'lg-and-up')} { @media #{map.get($display-breakpoints, 'lg-and-up')} {
@ -230,16 +128,4 @@ export default {
width: $dialog-desktop-width; width: $dialog-desktop-width;
} }
} }
.bottom-sheet-content[data-state="small"],
.bottom-sheet-content[data-state="open"],
.bottom-sheet-content[data-state="closed"] {
transition: top 0.3s ease-out;
}
.bottom-sheet-content[data-state="small"] {
@media #{map.get($display-breakpoints, 'lg-and-up')} {
top: 100px !important;
}
}
</style> </style>

View file

@ -28,8 +28,8 @@
<RoomUpgradePrompt v-if="roomUpgradeInfo" :roomId="roomId" :urgent="roomUpgradeInfo.urgent" :version="roomUpgradeInfo.version" /> <RoomUpgradePrompt v-if="roomUpgradeInfo" :roomId="roomId" :urgent="roomUpgradeInfo.urgent" :version="roomUpgradeInfo.version" />
<SendAttachmentsLayout <SendAttachmentsLayout
v-if="room && useFileModeNonAdmin" v-if="room && useFileModeNonAdmin"
:room="room" :room="room"
v-on:pick-file="showAttachmentPicker(false)" v-on:pick-file="showAttachmentPicker(false)"
v-on:add-files="(files) => addAttachments(files)" v-on:add-files="(files) => addAttachments(files)"
@ -42,24 +42,24 @@
<div v-if="!useVoiceMode && !useFileModeNonAdmin" :class="{'chat-content': true, 'flex-grow-1': true, 'flex-shrink-1': true, 'invisible': !initialLoadDone}" ref="chatContainer" <div v-if="!useVoiceMode && !useFileModeNonAdmin" :class="{'chat-content': true, 'flex-grow-1': true, 'flex-shrink-1': true, 'invisible': !initialLoadDone}" ref="chatContainer"
v-on:scroll="onScroll" @click="closeContextMenusIfOpen"> v-on:scroll="onScroll" @click="closeContextMenusIfOpen">
<div ref="messageOperationsStrut" class="message-operations-strut"> <div ref="messageOperationsStrut" class="message-operations-strut">
<component :is="messageOperationsComponent" ref="messageOperations" :style="opStyle" <component :is="messageOperationsComponent" ref="messageOperations" :style="opStyle"
v-if="showMessageOperations" v-if="showMessageOperations"
v-on:close="showContextMenu = false;" v-on:close="showContextMenu = false;"
:emojis="recentEmojis" :emojis="recentEmojis"
:originalEvent="selectedEvent" :originalEvent="selectedEvent"
:timelineSet="timelineSet" :timelineSet="timelineSet"
v-on:pin="pin(selectedEvent)" v-on:pin="pin(selectedEvent)"
v-on:unpin="unpin(selectedEvent)" v-on:unpin="unpin(selectedEvent)"
v-on:addreaction="addReaction" v-on:addreaction="addReaction"
v-on:addquickreaction="addQuickReaction" v-on:addquickreaction="addQuickReaction"
v-on:addreply="addReply(selectedEvent)" v-on:addreply="addReply(selectedEvent)"
v-on:edit="edit(selectedEvent)" v-on:edit="edit(selectedEvent)"
v-on:redact="redact(selectedEvent)" v-on:redact="redact(selectedEvent)"
v-on:download="download(selectedEvent)" v-on:download="download(selectedEvent)"
v-on:more=" v-on:more="
isEmojiQuickReaction=true; isEmojiQuickReaction=true;
showMoreMessageOperations({event: selectedEvent, anchor: $event.anchor}) showMoreMessageOperations({event: selectedEvent, anchor: $event.anchor})
" "
:userCanSendReactionAndAnswerPoll="$matrix.userCanSendReactionAndAnswerPollInCurrentRoom" :userCanSendReactionAndAnswerPoll="$matrix.userCanSendReactionAndAnswerPollInCurrentRoom"
:userCanSendMessage="$matrix.userCanSendMessageInCurrentRoom" :userCanSendMessage="$matrix.userCanSendMessageInCurrentRoom"
:userCanPin="canCreatePoll" :userCanPin="canCreatePoll"
@ -75,7 +75,7 @@
<WelcomeHeaderChannelUser v-if="retentionTimer && !roomWelcomeHeader && newlyJoinedRoom" /> <WelcomeHeaderChannelUser v-if="retentionTimer && !roomWelcomeHeader && newlyJoinedRoom" />
<div v-for="(event) in filteredEvents" :key="event.getId()" :eventId="event !== ROOM_READ_MARKER_EVENT_PLACEHOLDER ? event.getId() : undefined"> <div v-for="(event) in filteredEvents" :key="event.getId()" :eventId="event !== ROOM_READ_MARKER_EVENT_PLACEHOLDER ? event.getId() : undefined">
<!-- DAY Marker, shown for every new day in the timeline --> <!-- DAY Marker, shown for every new day in the timeline -->
<div v-if="!reverseOrder && showDayMarkerBeforeEvent(event) && !!event.component && event !== ROOM_READ_MARKER_EVENT_PLACEHOLDER" class="day-marker"><div class="line"></div><div class="text">{{ dayForEvent(event) }}</div><div class="line"></div></div> <div v-if="!reverseOrder && showDayMarkerBeforeEvent(event) && !!event.component && event !== ROOM_READ_MARKER_EVENT_PLACEHOLDER" class="day-marker"><div class="line"></div><div class="text">{{ dayForEvent(event) }}</div><div class="line"></div></div>
@ -234,8 +234,8 @@
accept="image/*,audio/*,video/*,.mp3,.mp4,.wav,.m4a,.pdf,application/pdf,.apk,application/vnd.android.package-archive,.ipa,.zip,application/zip,application/x-zip-compressed,multipart/x-zip" class="d-none" multiple/> accept="image/*,audio/*,video/*,.mp3,.mp4,.wav,.m4a,.pdf,application/pdf,.apk,application/vnd.android.package-archive,.ipa,.zip,application/zip,application/x-zip-compressed,multipart/x-zip" class="d-none" multiple/>
</form> </form>
<SendAttachmentsLayout <SendAttachmentsLayout
v-if="uploadBatch && uploadBatch.attachments.length > 0 && !useFileModeNonAdmin" v-if="uploadBatch && uploadBatch.attachments.length > 0 && !useFileModeNonAdmin"
:room="room" :room="room"
v-on:pick-file="showAttachmentPicker(false)" v-on:pick-file="showAttachmentPicker(false)"
v-on:add-files="(files) => addAttachments(files)" v-on:add-files="(files) => addAttachments(files)"
@ -244,15 +244,15 @@
:title="room.name" :title="room.name"
/> />
<BottomSheet ref="messageOperationsSheet" halfY="0.1"> <BottomSheet ref="messageOperationsSheet">
<EmojiPicker ref="emojiPicker" <EmojiPicker ref="emojiPicker"
:native="true" :native="true"
@select="emojiSelected" @select="emojiSelected"
:additional-groups="additionalEmojiGroups" :additional-groups="additionalEmojiGroups"
:group-names="emojiGroupNames" :group-names="emojiGroupNames"
:group-icons="additionalEmojiGroupIcons" :group-icons="additionalEmojiGroupIcons"
:group-order="['recently_used']" :group-order="['recently_used']"
disable-skin-tones disable-skin-tones
:static-texts="{ placeholder: $t('emoji.search')}"/> :static-texts="{ placeholder: $t('emoji.search')}"/>
</BottomSheet> </BottomSheet>
@ -885,7 +885,7 @@ export default {
var rectOps = this.$refs.messageOperations.$el.getBoundingClientRect(); var rectOps = this.$refs.messageOperations.$el.getBoundingClientRect();
if (this.room.displayType == ROOM_TYPE_CHANNEL) { if (this.room.displayType == ROOM_TYPE_CHANNEL) {
top = rectAnchor.top - rectChat.top; top = rectAnchor.top - rectChat.top;
let right = rectChat.right - rectAnchor.right; let right = rectChat.right - rectAnchor.right;
this.opStyle = "top:" + top + "px;right:" + right + "px"; this.opStyle = "top:" + top + "px;right:" + right + "px";
return; return;
} else { } else {
@ -994,7 +994,7 @@ export default {
onRoomJoined(initialEventId) { onRoomJoined(initialEventId) {
// If our own join event is less than a minute old, consider this a "newly joined" room. // If our own join event is less than a minute old, consider this a "newly joined" room.
// //
// Previously tried to look at initialEventId, but it seems like "this.room.getEventReadUpTo(this.$matrix.currentUserId, false)" // Previously tried to look at initialEventId, but it seems like "this.room.getEventReadUpTo(this.$matrix.currentUserId, false)"
// always returns an event id? Strange. I would expect it to be null on a fresh room. // always returns an event id? Strange. I would expect it to be null on a fresh room.
// //
@ -1101,8 +1101,8 @@ export default {
this.$navigation.push( this.$navigation.push(
{ {
name: "Join", name: "Join",
params: { params: {
roomId: util.sanitizeRoomId(this.roomAliasOrId), roomId: util.sanitizeRoomId(this.roomAliasOrId),
join: this.$route.params.join join: this.$route.params.join
}, },
query: this.$route.query query: this.$route.query

View file

@ -1,9 +1,7 @@
<template> <template>
<BottomSheet <BottomSheet
class="room-info-bottom-sheet" class="room-info-bottom-sheet"
:halfY="0.12"
ref="sheet" ref="sheet"
:showCloseButton="false"
> >
<div class="room-info-sheet" ref="roomInfoSheetContent"> <div class="room-info-sheet" ref="roomInfoSheetContent">
<room-list v-on:close="close" v-on:newroom="createRoom" :showCreate="!$config.hide_add_room_on_home" /> <room-list v-on:close="close" v-on:newroom="createRoom" :showCreate="!$config.hide_add_room_on_home" />

View file

@ -63,10 +63,6 @@ export default {
</style> </style>
<style lang="scss"> <style lang="scss">
.sheetContent {
top: 40px !important;
padding: 0 20px 20px 20px !important;
}
.sticker-picker { .sticker-picker {
z-index: 10; z-index: 10;

View file

@ -19,10 +19,7 @@
<span>{{ $t("message.seen_by_count", seenBy.length) }}</span> <span>{{ $t("message.seen_by_count", seenBy.length) }}</span>
</v-tooltip> </v-tooltip>
</div> </div>
<BottomSheet <BottomSheet ref="seenByListBottomSheet">
:halfY="0.12"
ref="seenByListBottomSheet"
>
<v-list> <v-list>
<v-list-subheader class="text-uppercase"> {{ $t("message.seen_by") }}</v-list-subheader> <v-list-subheader class="text-uppercase"> {{ $t("message.seen_by") }}</v-list-subheader>
<v-list-item v-for="(member, index) in seenBy" :key="index" class="text-left"> <v-list-item v-for="(member, index) in seenBy" :key="index" class="text-left">