UX: implements swipe on row channel (#23436)

On mobile swiping a channel row will now show a "Remove" option. Holding this to the end will now remove this row from your list of followed direct message channels.

Co-authored-by: chapoi <101828855+chapoi@users.noreply.github.com>
This commit is contained in:
Joffrey JAFFEUX 2023-09-11 14:51:13 +02:00 committed by GitHub
parent 87d0336f05
commit b8d5f951f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 621 additions and 366 deletions

View File

@ -43,7 +43,14 @@
</LinkTo>
</div>
<div id="public-channels" class={{this.publicChannelClasses}}>
<div
id="public-channels"
class={{concat-class
"channels-list-container"
"public-channels"
(if this.inSidebar "collapsible-sidebar-section")
}}
>
{{#if this.publicChannelsEmpty}}
<div class="public-channel-empty-message">
<span class="channel-title">{{i18n "chat.no_public_channels"}}</span>

View File

@ -65,12 +65,6 @@ export default class ChannelsList extends Component {
return this.chat.userCanDirectMessage;
}
get publicChannelClasses() {
return `channels-list-container public-channels ${
this.inSidebar ? "collapsible-sidebar-section" : ""
}`;
}
get displayPublicChannels() {
if (!this.siteSettings.enable_public_channels) {
return false;

View File

@ -0,0 +1,252 @@
import { inject as service } from "@ember/service";
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { LinkTo } from "@ember/routing";
import concatClass from "discourse/helpers/concat-class";
import eq from "truth-helpers/helpers/eq";
import and from "truth-helpers/helpers/and";
import ChatChannelTitle from "discourse/plugins/chat/discourse/components/chat-channel-title";
import ChatChannelMetadata from "discourse/plugins/chat/discourse/components/chat-channel-metadata";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import ToggleChannelMembershipButton from "discourse/plugins/chat/discourse/components/toggle-channel-membership-button";
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
import { hash } from "@ember/helper";
import I18n from "I18n";
import { modifier } from "ember-modifier";
import { bind } from "discourse-common/utils/decorators";
import { tracked } from "@glimmer/tracking";
import discourseLater from "discourse-common/lib/later";
import { cancel } from "@ember/runloop";
import { popupAjaxError } from "discourse/lib/ajax-error";
const RESET_CLASS = "-reset";
const FADEOUT_CLASS = "-fade-out";
export default class ChatChannelRow extends Component {
<template>
{{! template-lint-disable modifier-name-case }}
<LinkTo
@route="chat.channel"
@models={{@channel.routeModels}}
class={{concatClass
"chat-channel-row"
(if @channel.focused "focused")
(if @channel.currentUserMembership.muted "muted")
(if @options.leaveButton "can-leave")
(if (eq this.chat.activeChannel.id @channel.id) "active")
(if this.channelHasUnread "has-unread")
}}
tabindex="0"
data-chat-channel-id={{@channel.id}}
{{didInsert this.startTrackingStatus}}
{{willDestroy this.stopTrackingStatus}}
{{(if this.shouldHandleSwipe (modifier this.registerSwipableRow))}}
{{(if this.shouldHandleSwipe (modifier this.handleSwipe))}}
{{(if this.shouldRemoveChannel (modifier this.onRemoveChannel))}}
{{(if this.shouldResetRow (modifier this.onResetRow))}}
>
<ChatChannelTitle @channel={{@channel}} />
<ChatChannelMetadata @channel={{@channel}} @unreadIndicator={{true}} />
{{#if
(and @options.leaveButton @channel.isFollowing this.site.desktopView)
}}
<ToggleChannelMembershipButton
@channel={{@channel}}
@options={{hash
leaveClass="btn-flat chat-channel-leave-btn"
labelType="none"
leaveIcon="times"
leaveTitle=(if
@channel.isDirectMessageChannel
this.leaveDirectMessageLabel
this.leaveChannelLabel
)
}}
/>
{{/if}}
{{#if this.shouldHandleSwipe}}
<div
class={{concatClass
"chat-channel-row__action-btn"
(if this.isCancelling "-cancel" "-leave")
}}
{{this.registerActionButton}}
{{this.positionActionButton}}
>
{{#if this.isCancelling}}
{{this.cancelActionLabel}}
{{else}}
{{this.removeActionLabel}}
{{/if}}
</div>
{{/if}}
</LinkTo>
</template>
@service router;
@service chat;
@service capabilities;
@service currentUser;
@service site;
@service api;
@tracked shouldRemoveChannel = false;
@tracked hasReachedThreshold = false;
@tracked isCancelling = false;
@tracked shouldResetRow = false;
@tracked actionButton;
@tracked swipableRow;
positionActionButton = modifier((element) => {
element.style.left = "100%";
});
registerActionButton = modifier((element) => {
this.actionButton = element;
});
registerSwipableRow = modifier((element) => {
this.swipableRow = element;
});
onRemoveChannel = modifier(() => {
this.swipableRow.classList.add(FADEOUT_CLASS);
const handler = discourseLater(() => {
this.chat.unfollowChannel(this.args.channel).catch(popupAjaxError);
}, 250);
return () => {
cancel(handler);
};
});
handleSwipe = modifier((element) => {
element.addEventListener("touchstart", this.onSwipeStart, {
passive: true,
});
element.addEventListener("touchmove", this.onSwipe);
element.addEventListener("touchend", this.onSwipeEnd);
return () => {
element.removeEventListener("touchstart", this.onSwipeStart);
element.removeEventListener("touchmove", this.onSwipe);
element.removeEventListener("touchend", this.onSwipeEnd);
};
});
onResetRow = modifier(() => {
this.swipableRow.classList.add(RESET_CLASS);
this.swipableRow.style.left = "0px";
const handler = discourseLater(() => {
this.isCancelling = false;
this.hasReachedThreshold = false;
this.shouldResetRow = false;
this.swipableRow.classList.remove(RESET_CLASS);
}, 250);
return () => {
cancel(handler);
this.swipableRow.classList.remove(RESET_CLASS);
};
});
_lastX = null;
_towardsThreshold = false;
@bind
onSwipeStart(event) {
this.hasReachedThreshold = false;
this.isCancelling = false;
this._lastX = this.initialX = event.changedTouches[0].screenX;
}
@bind
onSwipe(event) {
event.preventDefault();
const touchX = event.changedTouches[0].screenX;
const diff = this.initialX - touchX;
// we don't state to be too sensitive to the touch
if (Math.abs(this._lastX - touchX) > 5) {
this._towardsThreshold = this._lastX >= touchX;
this._lastX = touchX;
}
// ensures we will go back to the initial position when swiping very fast
if (diff < 10) {
this.isCancelling = false;
this.hasReachedThreshold = false;
this.swipableRow.style.left = "0px";
return;
}
if (diff >= window.innerWidth / 3) {
this.isCancelling = false;
this.hasReachedThreshold = true;
return;
} else {
this.isCancelling = !this._towardsThreshold;
}
this.actionButton.style.width = diff + "px";
this.swipableRow.style.left = -(this.initialX - touchX) + "px";
}
@bind
onSwipeEnd(event) {
this._lastX = null;
const diff = this.initialX - event.changedTouches[0].screenX;
if (diff >= window.innerWidth / 3) {
this.swipableRow.style.left = "0px";
this.shouldRemoveChannel = true;
return;
}
this.isCancelling = true;
this.shouldResetRow = true;
}
get shouldHandleSwipe() {
return this.capabilities.touch && this.args.channel.isDirectMessageChannel;
}
get cancelActionLabel() {
return I18n.t("cancel_value");
}
get removeActionLabel() {
return I18n.t("chat.remove");
}
get leaveDirectMessageLabel() {
return I18n.t("chat.direct_messages.leave");
}
get leaveChannelLabel() {
return I18n.t("chat.channel_settings.leave_channel");
}
get channelHasUnread() {
return this.args.channel.tracking.unreadCount > 0;
}
get #firstDirectMessageUser() {
return this.args.channel?.chatable?.users?.firstObject;
}
@action
startTrackingStatus() {
this.#firstDirectMessageUser?.trackStatus();
}
@action
stopTrackingStatus() {
this.#firstDirectMessageUser?.stopTrackingStatus();
}
}

View File

@ -1,35 +0,0 @@
<LinkTo
@route="chat.channel"
@models={{@channel.routeModels}}
class={{concat-class
"chat-channel-row"
(if @channel.focused "focused")
(if @channel.currentUserMembership.muted "muted")
(if @options.leaveButton "can-leave")
(if (eq this.chat.activeChannel.id @channel.id) "active")
(if this.channelHasUnread "has-unread")
}}
tabindex="0"
data-chat-channel-id={{@channel.id}}
{{did-insert this.startTrackingStatus}}
{{will-destroy this.stopTrackingStatus}}
>
<ChatChannelTitle @channel={{@channel}} />
<ChatChannelMetadata @channel={{@channel}} @unreadIndicator={{true}} />
{{#if (and @options.leaveButton @channel.isFollowing this.site.desktopView)}}
<ToggleChannelMembershipButton
@channel={{@channel}}
@options={{hash
leaveClass="btn-flat chat-channel-leave-btn"
labelType="none"
leaveIcon="times"
leaveTitle=(if
@channel.isDirectMessageChannel
(i18n "chat.direct_messages.leave")
(i18n "chat.channel_settings.leave_channel")
)
}}
/>
{{/if}}
</LinkTo>

View File

@ -1,28 +0,0 @@
import { inject as service } from "@ember/service";
import Component from "@glimmer/component";
import { action } from "@ember/object";
export default class ChatChannelRow extends Component {
@service router;
@service chat;
@service currentUser;
@service site;
@action
startTrackingStatus() {
this.#firstDirectMessageUser?.trackStatus();
}
@action
stopTrackingStatus() {
this.#firstDirectMessageUser?.stopTrackingStatus();
}
get channelHasUnread() {
return this.args.channel.tracking.unreadCount > 0;
}
get #firstDirectMessageUser() {
return this.args.channel?.chatable?.users?.firstObject;
}
}

View File

@ -12,6 +12,26 @@ import discourseDebounce from "discourse-common/lib/debounce";
import { bind } from "discourse-common/utils/decorators";
import { updateUserStatusOnMention } from "discourse/lib/update-user-status-on-mention";
import { tracked } from "@glimmer/tracking";
import ChatMessageSeparatorDate from "discourse/plugins/chat/discourse/components/chat-message-separator-date";
import ChatMessageSeparatorNew from "discourse/plugins/chat/discourse/components/chat-message-separator-new";
import concatClass from "discourse/helpers/concat-class";
import DButton from "discourse/components/d-button";
import ChatMessageInReplyToIndicator from "discourse/plugins/chat/discourse/components/chat-message-in-reply-to-indicator";
import ChatMessageLeftGutter from "discourse/plugins/chat/discourse/components/chat/message/left-gutter";
import ChatMessageAvatar from "discourse/plugins/chat/discourse/components/chat/message/avatar";
import ChatMessageError from "discourse/plugins/chat/discourse/components/chat/message/error";
import ChatMessageInfo from "discourse/plugins/chat/discourse/components/chat/message/info";
import ChatMessageText from "discourse/plugins/chat/discourse/components/chat-message-text";
import ChatMessageReaction from "discourse/plugins/chat/discourse/components/chat-message-reaction";
import ChatMessageThreadIndicator from "discourse/plugins/chat/discourse/components/chat-message-thread-indicator";
import eq from "truth-helpers/helpers/eq";
import not from "truth-helpers/helpers/not";
import { on } from "@ember/modifier";
import { Input } from "@ember/component";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
import ChatOnLongPress from "discourse/plugins/chat/discourse/modifiers/chat/on-long-press";
let _chatMessageDecorators = [];
let _tippyInstances = [];
@ -28,6 +48,139 @@ export const MENTION_KEYWORDS = ["here", "all"];
export const MESSAGE_CONTEXT_THREAD = "thread";
export default class ChatMessage extends Component {
<template>
{{! template-lint-disable no-invalid-interactive }}
{{! template-lint-disable modifier-name-case }}
{{#if this.shouldRender}}
{{#if (eq @context "channel")}}
<ChatMessageSeparatorDate
@fetchMessagesByDate={{@fetchMessagesByDate}}
@message={{@message}}
/>
<ChatMessageSeparatorNew @message={{@message}} />
{{/if}}
<div
class={{concatClass
"chat-message-container"
(if this.pane.selectingMessages "-selectable")
(if @message.highlighted "-highlighted")
(if (eq @message.user.id this.currentUser.id) "is-by-current-user")
(if @message.staged "-staged" "-persisted")
(if this.hasActiveState "-active")
(if @message.bookmark "-bookmarked")
(if @message.deletedAt "-deleted")
(if @message.selected "-selected")
(if @message.error "-errored")
(if this.showThreadIndicator "has-thread-indicator")
(if this.hideUserInfo "-user-info-hidden")
(if this.hasReply "has-reply")
}}
data-id={{@message.id}}
data-thread-id={{@message.thread.id}}
{{didInsert this.didInsertMessage}}
{{didUpdate this.didUpdateMessageId @message.id}}
{{didUpdate this.didUpdateMessageVersion @message.version}}
{{willDestroy this.willDestroyMessage}}
{{on "mouseenter" this.onMouseEnter passive=true}}
{{on "mouseleave" this.onMouseLeave passive=true}}
{{on "mousemove" this.onMouseMove passive=true}}
{{ChatOnLongPress
this.onLongPressStart
this.onLongPressEnd
this.onLongPressCancel
}}
...attributes
>
{{#if this.show}}
{{#if this.pane.selectingMessages}}
<Input
@type="checkbox"
class="chat-message-selector"
@checked={{@message.selected}}
{{on "click" this.toggleChecked}}
/>
{{/if}}
{{#if this.deletedAndCollapsed}}
<div class="chat-message-text -deleted">
<DButton
@action={{this.expand}}
@translatedLabel={{this.deletedMessageLabel}}
class="btn-flat chat-message-expand"
/>
</div>
{{else if this.hiddenAndCollapsed}}
<div class="chat-message-text -hidden">
<DButton
@action={{this.expand}}
@label="chat.hidden"
class="btn-flat chat-message-expand"
/>
</div>
{{else}}
<div class="chat-message">
{{#unless this.hideReplyToInfo}}
<ChatMessageInReplyToIndicator @message={{@message}} />
{{/unless}}
{{#if this.hideUserInfo}}
<ChatMessageLeftGutter @message={{@message}} />
{{else}}
<ChatMessageAvatar @message={{@message}} />
{{/if}}
<div class="chat-message-content">
<ChatMessageInfo
@message={{@message}}
@show={{not this.hideUserInfo}}
/>
<ChatMessageText
@cooked={{@message.cooked}}
@uploads={{@message.uploads}}
@edited={{@message.edited}}
>
{{#if @message.reactions.length}}
<div class="chat-message-reaction-list">
{{#each @message.reactions as |reaction|}}
<ChatMessageReaction
@reaction={{reaction}}
@onReaction={{this.messageInteractor.react}}
@message={{@message}}
@showTooltip={{true}}
/>
{{/each}}
{{#if this.shouldRenderOpenEmojiPickerButton}}
<DButton
@action={{this.messageInteractor.openEmojiPicker}}
@icon="discourse-emojis"
@title="chat.react"
@forwardEvent={{true}}
class="chat-message-react-btn"
/>
{{/if}}
</div>
{{/if}}
</ChatMessageText>
<ChatMessageError
@message={{@message}}
@onRetry={{@resendStagedMessage}}
/>
</div>
{{#if this.showThreadIndicator}}
<ChatMessageThreadIndicator @message={{@message}} />
{{/if}}
</div>
{{/if}}
{{/if}}
</div>
{{/if}}
</template>
@service site;
@service dialog;
@service currentUser;

View File

@ -1,130 +0,0 @@
{{! template-lint-disable no-invalid-interactive }}
{{#if this.shouldRender}}
{{#if (eq @context "channel")}}
<ChatMessageSeparatorDate
@fetchMessagesByDate={{@fetchMessagesByDate}}
@message={{@message}}
/>
<ChatMessageSeparatorNew @message={{@message}} />
{{/if}}
<div
class={{concat-class
"chat-message-container"
(if this.pane.selectingMessages "-selectable")
(if @message.highlighted "-highlighted")
(if (eq @message.user.id this.currentUser.id) "is-by-current-user")
(if @message.staged "-staged" "-persisted")
(if this.hasActiveState "-active")
(if @message.bookmark "-bookmarked")
(if @message.deletedAt "-deleted")
(if @message.selected "-selected")
(if @message.error "-errored")
(if this.showThreadIndicator "has-thread-indicator")
(if this.hideUserInfo "-user-info-hidden")
(if this.hasReply "has-reply")
}}
data-id={{@message.id}}
data-thread-id={{@message.thread.id}}
{{did-insert this.didInsertMessage}}
{{did-update this.didUpdateMessageId @message.id}}
{{did-update this.didUpdateMessageVersion @message.version}}
{{will-destroy this.willDestroyMessage}}
{{on "mouseenter" this.onMouseEnter passive=true}}
{{on "mouseleave" this.onMouseLeave passive=true}}
{{on "mousemove" this.onMouseMove passive=true}}
{{chat/on-long-press
this.onLongPressStart
this.onLongPressEnd
this.onLongPressCancel
}}
...attributes
>
{{#if this.show}}
{{#if this.pane.selectingMessages}}
<Input
@type="checkbox"
class="chat-message-selector"
@checked={{@message.selected}}
{{on "click" this.toggleChecked}}
/>
{{/if}}
{{#if this.deletedAndCollapsed}}
<div class="chat-message-text -deleted">
<DButton
@action={{this.expand}}
@translatedLabel={{this.deletedMessageLabel}}
class="btn-flat chat-message-expand"
/>
</div>
{{else if this.hiddenAndCollapsed}}
<div class="chat-message-text -hidden">
<DButton
@action={{this.expand}}
@label="chat.hidden"
class="btn-flat chat-message-expand"
/>
</div>
{{else}}
<div class="chat-message">
{{#unless this.hideReplyToInfo}}
<ChatMessageInReplyToIndicator @message={{@message}} />
{{/unless}}
{{#if this.hideUserInfo}}
<Chat::Message::LeftGutter @message={{@message}} />
{{else}}
<Chat::Message::Avatar @message={{@message}} />
{{/if}}
<div class="chat-message-content">
<Chat::Message::Info
@message={{@message}}
@show={{not this.hideUserInfo}}
/>
<ChatMessageText
@cooked={{@message.cooked}}
@uploads={{@message.uploads}}
@edited={{@message.edited}}
>
{{#if @message.reactions.length}}
<div class="chat-message-reaction-list">
{{#each @message.reactions as |reaction|}}
<ChatMessageReaction
@reaction={{reaction}}
@onReaction={{this.messageInteractor.react}}
@message={{@message}}
@showTooltip={{true}}
/>
{{/each}}
{{#if this.shouldRenderOpenEmojiPickerButton}}
<DButton
@action={{this.messageInteractor.openEmojiPicker}}
@icon="discourse-emojis"
@title="chat.react"
@forwardEvent={{true}}
class="chat-message-react-btn"
/>
{{/if}}
</div>
{{/if}}
</ChatMessageText>
<Chat::Message::Error
@message={{@message}}
@onRetry={{@resendStagedMessage}}
/>
</div>
{{#if this.showThreadIndicator}}
<ChatMessageThreadIndicator @message={{@message}} />
{{/if}}
</div>
{{/if}}
{{/if}}
</div>
{{/if}}

View File

@ -0,0 +1,146 @@
.chat-channel-row {
align-items: center;
box-sizing: border-box;
display: flex;
justify-content: space-between;
position: relative;
cursor: pointer;
color: var(--primary-high);
@media (hover: none) {
&:hover,
&:focus {
background: transparent;
}
&:active {
background: var(--primary-low);
}
}
@media (hover: hover) {
&:hover,
&.active {
background: var(--primary-very-low);
}
&.can-leave:hover {
.toggle-channel-membership-button.-leave {
display: block;
> * {
pointer-events: auto;
}
}
.chat-channel-metadata {
display: none;
}
}
}
&:hover,
&.active {
.chat-channel-title {
&,
.category-chat-name,
.dm-usernames {
color: var(--primary);
}
.d-icon-lock {
background-color: var(--primary-low);
}
}
}
&:visited {
color: var(--primary-high);
}
&.muted {
opacity: 0.65;
}
.chat-channel-title {
&__users-count {
width: var(--channel-list-avatar-size);
height: var(--channel-list-avatar-size);
padding: 0;
font-size: var(--font-up-1);
justify-content: center;
}
&__avatar {
.chat-user-avatar {
img {
width: calc(var(--channel-list-avatar-size) - 2px);
height: calc(var(--channel-list-avatar-size) - 2px);
}
}
}
&__user-info {
@include ellipsis;
}
&__usernames {
display: flex;
align-items: center;
justify-content: start;
}
.user-status-message {
display: inline-block;
font-size: var(--font-down-2);
margin-right: 0.5rem;
&-description {
color: var(--primary-medium);
}
}
}
.chat-channel-metadata {
display: flex;
align-items: flex-end;
flex-direction: column;
margin-left: 0.5em;
&__date {
color: var(--primary-high);
font-size: var(--font-down-2);
white-space: nowrap;
}
.chat-channel-unread-indicator {
@include chat-unread-indicator;
display: flex;
align-items: center;
justify-content: center;
width: 8px;
height: 8px;
&.-urgent {
width: auto;
height: auto;
min-width: 0.6em;
padding: 0.3em 0.5em;
}
}
}
&.unfollowing {
opacity: 0;
}
.toggle-channel-membership-button.-leave {
display: none;
margin-left: auto;
}
.badge-wrapper {
align-items: center;
margin-right: 0;
}
.emoji {
margin-left: 0.3em;
}
}

View File

@ -88,151 +88,4 @@
padding-top: 1rem;
}
}
.chat-channel-row {
align-items: center;
box-sizing: border-box;
display: flex;
justify-content: space-between;
position: relative;
cursor: pointer;
color: var(--primary-high);
@media (hover: none) {
&:hover,
&:focus {
background: transparent;
}
&:active {
background: var(--primary-low);
}
}
@media (hover: hover) {
&:hover,
&.active {
background: var(--primary-very-low);
}
&.can-leave:hover {
.toggle-channel-membership-button.-leave {
display: block;
> * {
pointer-events: auto;
}
}
.chat-channel-metadata {
display: none;
}
}
}
&:hover,
&.active {
.chat-channel-title {
&,
.category-chat-name,
.dm-usernames {
color: var(--primary);
}
.d-icon-lock {
background-color: var(--primary-low);
}
}
}
&:visited {
color: var(--primary-high);
}
&.muted {
opacity: 0.65;
}
.chat-channel-title {
&__users-count {
width: var(--channel-list-avatar-size);
height: var(--channel-list-avatar-size);
padding: 0;
font-size: var(--font-up-1);
justify-content: center;
}
&__avatar {
.chat-user-avatar {
img {
width: calc(var(--channel-list-avatar-size) - 2px);
height: calc(var(--channel-list-avatar-size) - 2px);
}
}
}
&__user-info {
@include ellipsis;
}
&__usernames {
display: flex;
align-items: center;
justify-content: start;
}
.user-status-message {
display: inline-block;
font-size: var(--font-down-2);
margin-right: 0.5rem;
&-description {
color: var(--primary-medium);
}
}
}
.chat-channel-metadata {
display: flex;
align-items: flex-end;
flex-direction: column;
margin-left: 0.5em;
&__date {
color: var(--primary-high);
font-size: var(--font-down-2);
white-space: nowrap;
}
.chat-channel-unread-indicator {
@include chat-unread-indicator;
display: flex;
align-items: center;
justify-content: center;
width: 8px;
height: 8px;
&.-urgent {
width: auto;
height: auto;
min-width: 0.6em;
padding: 0.3em 0.5em;
}
}
}
&.unfollowing {
opacity: 0;
}
.toggle-channel-membership-button.-leave {
display: none;
margin-left: auto;
}
.badge-wrapper {
align-items: center;
margin-right: 0;
}
.emoji {
margin-left: 0.3em;
}
}
}

View File

@ -63,3 +63,4 @@
@import "chat-modal-channel-summary";
@import "chat-modal-move-message-to-channel";
@import "chat-scroll-to-bottom";
@import "chat-channel-row";

View File

@ -0,0 +1,54 @@
.chat-channel-row {
height: 4em;
margin: 0;
padding: 0 1.5rem;
border-radius: 0;
border-bottom: 1px solid var(--primary-low);
transition: height 0.25s ease-in-out, opacity 0.25s ease-out;
transform-origin: top center;
will-change: height, left;
&__action-btn {
display: flex;
align-items: center;
position: absolute;
top: 0px;
bottom: 0px;
padding-inline: 1rem;
&.-cancel {
background: var(--primary-very-low);
color: var(--primary);
}
&.-leave {
background: var(--danger);
color: var(--primary-very-low);
}
}
&__action-btn-icon {
margin-left: 0.5rem;
}
&.-fade-out {
background-color: var(--danger-low);
height: 0 !important;
overflow: hidden;
opacity: 0.5 !important;
}
&.-reset {
transition: left 0.25s ease-in-out;
}
.chat-channel-metadata {
.chat-channel-unread-indicator {
font-size: var(--font-down-2);
margin-top: 0.25rem;
}
&__date {
font-size: var(--font-down-2);
}
}
}

View File

@ -10,24 +10,7 @@
.channels-list-container {
background: var(--secondary);
}
.chat-channel-row {
height: 4em;
margin: 0;
padding: 0 1.5rem;
border-radius: 0;
border-bottom: 1px solid var(--primary-low);
.chat-channel-metadata {
.chat-channel-unread-indicator {
font-size: var(--font-down-2);
margin-top: 0.25rem;
}
&__date {
font-size: var(--font-down-2);
}
}
overflow: hidden;
}
.chat-channel-divider {

View File

@ -14,3 +14,4 @@
@import "chat-modal-thread-settings";
@import "chat-message-thread-indicator";
@import "chat-message-creator";
@import "chat-channel-row";

View File

@ -83,6 +83,7 @@ en:
click_to_join: "Click here to view available channels."
close: "Close"
remove: "Remove"
collapse: "Collapse Chat Drawer"
expand: "Expand Chat Drawer"
confirm_flag: "Are you sure you want to flag %{username}'s message?"

View File

@ -27,7 +27,10 @@ RSpec.describe "List channels | mobile", type: :system, mobile: true do
context "when not member of the channel" do
it "doesnt show the channel" do
visit("/chat")
expect(page.find(".public-channels")).to have_no_content(category_channel_1.name)
expect(page.find(".public-channels", visible: :all)).to have_no_content(
category_channel_1.name,
)
end
end
end