Feature: ResizableTextArea in widget and dashboard (#969)
* Create ResizableTextArea component * Rubocop fixes and spec fixes Co-authored-by: Sojan <sojan@pepalo.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
border-bottom: 0;
|
||||
margin: $space-normal;
|
||||
margin-top: 0;
|
||||
max-height: $space-jumbo * 2;
|
||||
max-height: $space-mega * 3;
|
||||
transition: height 2s $ease-in-out-cubic;
|
||||
|
||||
.reply-box__top {
|
||||
@@ -68,13 +68,14 @@
|
||||
padding: 0 $space-small;
|
||||
}
|
||||
|
||||
>textarea {
|
||||
> textarea {
|
||||
@include ghost-input();
|
||||
@include margin(0);
|
||||
background: transparent;
|
||||
// Override min-height : 50px in foundation
|
||||
//
|
||||
min-height: 1rem;
|
||||
max-height: $space-mega * 2.4;
|
||||
min-height: 4rem;
|
||||
resize: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,12 @@
|
||||
v-on-clickaway="hideEmojiPicker"
|
||||
:on-click="emojiOnClick"
|
||||
/>
|
||||
<textarea
|
||||
<resizable-text-area
|
||||
ref="messageInput"
|
||||
v-model="message"
|
||||
rows="1"
|
||||
class="input"
|
||||
type="text"
|
||||
:placeholder="$t(messagePlaceHolder())"
|
||||
:min-height="4"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
@@ -93,12 +92,14 @@ import FileUpload from 'vue-upload-component';
|
||||
|
||||
import EmojiInput from '../emoji/EmojiInput';
|
||||
import CannedResponse from './CannedResponse';
|
||||
import ResizableTextArea from 'shared/components/ResizableTextArea';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EmojiInput,
|
||||
CannedResponse,
|
||||
FileUpload,
|
||||
ResizableTextArea,
|
||||
},
|
||||
mixins: [clickaway],
|
||||
data() {
|
||||
|
||||
56
app/javascript/shared/components/ResizableTextArea.vue
Normal file
56
app/javascript/shared/components/ResizableTextArea.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<textarea
|
||||
ref="textarea"
|
||||
:placeholder="placeholder"
|
||||
:value="value"
|
||||
@input="onInput"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
minHeight: {
|
||||
type: Number,
|
||||
default: 3.2,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value() {
|
||||
this.resizeTextarea();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
resizeTextarea() {
|
||||
if (!this.value) {
|
||||
this.$el.style.height = `${this.minHeight}rem`;
|
||||
} else {
|
||||
this.$el.style.height = `${this.$el.scrollHeight}px`;
|
||||
}
|
||||
},
|
||||
onInput(event) {
|
||||
this.$emit('input', event.target.value);
|
||||
this.resizeTextarea();
|
||||
},
|
||||
onBlur() {
|
||||
this.$emit('blur');
|
||||
},
|
||||
onFocus() {
|
||||
this.$emit('focus');
|
||||
},
|
||||
focus() {
|
||||
this.$refs.textarea.focus();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,56 +0,0 @@
|
||||
<template>
|
||||
<resizable-textarea>
|
||||
<textarea
|
||||
class="form-input user-message-input"
|
||||
:placeholder="placeholder"
|
||||
:value="value"
|
||||
@input="$emit('input', $event.target.value)"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
</resizable-textarea>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ResizableTextarea from 'widget/components/ResizableTextarea.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ResizableTextarea,
|
||||
},
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onBlur() {
|
||||
this.toggleTyping('off');
|
||||
},
|
||||
onFocus() {
|
||||
this.toggleTyping('on');
|
||||
},
|
||||
toggleTyping(typingStatus) {
|
||||
this.$store.dispatch('conversation/toggleUserTyping', { typingStatus });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="scss">
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
|
||||
.user-message-input {
|
||||
border: 0;
|
||||
height: $space-large;
|
||||
min-height: $space-large;
|
||||
resize: none;
|
||||
padding-top: $space-small;
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,11 @@
|
||||
<template>
|
||||
<div class="chat-message--input">
|
||||
<chat-input-area
|
||||
<resizable-text-area
|
||||
v-model="userInput"
|
||||
:placeholder="$t('CHAT_PLACEHOLDER')"
|
||||
class="form-input user-message-input"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
<div class="button-wrap">
|
||||
<chat-attachment-button
|
||||
@@ -34,7 +37,7 @@ import emojione from 'emojione';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import ChatSendButton from 'widget/components/ChatSendButton.vue';
|
||||
import ChatAttachmentButton from 'widget/components/ChatAttachment.vue';
|
||||
import ChatInputArea from 'widget/components/ChatInputArea.vue';
|
||||
import ResizableTextArea from 'shared/components/ResizableTextArea';
|
||||
import EmojiInput from 'dashboard/components/widgets/emoji/EmojiInput';
|
||||
|
||||
export default {
|
||||
@@ -42,8 +45,8 @@ export default {
|
||||
components: {
|
||||
ChatAttachmentButton,
|
||||
ChatSendButton,
|
||||
ChatInputArea,
|
||||
EmojiInput,
|
||||
ResizableTextArea,
|
||||
},
|
||||
mixins: [clickaway],
|
||||
props: {
|
||||
@@ -109,6 +112,16 @@ export default {
|
||||
`${this.userInput}${emoji.shortname} `
|
||||
);
|
||||
},
|
||||
|
||||
onBlur() {
|
||||
this.toggleTyping('off');
|
||||
},
|
||||
onFocus() {
|
||||
this.toggleTyping('on');
|
||||
},
|
||||
toggleTyping(typingStatus) {
|
||||
this.$store.dispatch('conversation/toggleUserTyping', { typingStatus });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -140,4 +153,13 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.user-message-input {
|
||||
border: 0;
|
||||
height: $space-large;
|
||||
min-height: $space-large;
|
||||
max-height: 2.4 * $space-mega;
|
||||
resize: none;
|
||||
padding-top: $space-small;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<script>
|
||||
export default {
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.$el.setAttribute(
|
||||
'style',
|
||||
`height: ${this.$el.scrollHeight}px;overflow-y:hidden;`
|
||||
);
|
||||
});
|
||||
|
||||
this.$el.addEventListener('input', this.resizeTextarea);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$el.removeEventListener('input', this.resizeTextarea);
|
||||
},
|
||||
methods: {
|
||||
resizeTextarea(event) {
|
||||
event.target.style.height = '3.2rem';
|
||||
event.target.style.height = `${event.target.scrollHeight}px`;
|
||||
},
|
||||
},
|
||||
render() {
|
||||
return this.$slots.default[0];
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -43,6 +43,7 @@ export default {
|
||||
line-height: 1.5;
|
||||
padding: $space-slab $space-normal $space-slab $space-normal;
|
||||
text-align: left;
|
||||
word-break: break-word;
|
||||
|
||||
> a {
|
||||
color: $color-primary;
|
||||
|
||||
@@ -80,7 +80,7 @@ class MailPresenter < SimpleDelegator
|
||||
|
||||
{
|
||||
reply: content[0..(index - 1)].strip,
|
||||
quoted_text: content[index..-1].strip
|
||||
quoted_text: content[index..].strip
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user