144 lines
3.2 KiB
Vue
144 lines
3.2 KiB
Vue
<template>
|
|
<div class="shared-textarea" :class="{ 'has-value': model, 'is-focused': focused, 'is-disabled': disabled, 'is-error': error }">
|
|
<label v-if="label" :for="id">
|
|
<span>{{ label }}</span>
|
|
<span v-if="maxlength" class="char-count">{{ (model || '').length }} / {{ maxlength }}</span>
|
|
</label>
|
|
<div class="textarea-wrap">
|
|
<textarea
|
|
:id="id"
|
|
:placeholder="placeholder"
|
|
:disabled="disabled"
|
|
:maxlength="maxlength"
|
|
:rows="rows"
|
|
v-model="model"
|
|
@focus="focused = true"
|
|
@blur="focused = false"
|
|
/>
|
|
<span class="underline" />
|
|
</div>
|
|
<span v-if="error" class="error-msg">{{ error }}</span>
|
|
<span v-else-if="hint" class="hint">{{ hint }}</span>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref } from 'vue'
|
|
|
|
const props = defineProps({
|
|
id: String,
|
|
label: String,
|
|
placeholder: String,
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
maxlength: Number,
|
|
rows: {
|
|
type: Number,
|
|
default: 4,
|
|
},
|
|
error: String,
|
|
hint: String,
|
|
})
|
|
|
|
const model = defineModel()
|
|
const focused = ref(false)
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.shared-textarea {
|
|
--t-duration: 250ms;
|
|
--t-ease: var(--ease-out-quad);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: rs(6px);
|
|
|
|
label {
|
|
@include label;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
opacity: 0.45;
|
|
transition: opacity var(--t-duration) var(--t-ease);
|
|
|
|
.char-count {
|
|
font-weight: 400;
|
|
}
|
|
}
|
|
|
|
.textarea-wrap {
|
|
position: relative;
|
|
|
|
textarea {
|
|
@include p;
|
|
width: 100%;
|
|
background: transparent;
|
|
border: none;
|
|
border-bottom: rs(1px) solid var(--grey-400);
|
|
padding: rs(8px) 0;
|
|
color: var(--theme-fg);
|
|
outline: none;
|
|
resize: vertical;
|
|
caret-color: var(--theme-contrast);
|
|
min-height: rs(80px);
|
|
|
|
&::placeholder {
|
|
color: var(--theme-fg);
|
|
opacity: 0.25;
|
|
}
|
|
}
|
|
|
|
.underline {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: rs(1px);
|
|
background: var(--theme-contrast);
|
|
transform: scaleX(0);
|
|
transform-origin: left;
|
|
transition: transform var(--t-duration) var(--t-ease);
|
|
}
|
|
}
|
|
|
|
.error-msg,
|
|
.hint {
|
|
@include label;
|
|
font-size: rs(11px);
|
|
}
|
|
|
|
.hint {
|
|
opacity: 0.35;
|
|
}
|
|
|
|
&.is-focused {
|
|
label {
|
|
opacity: 1;
|
|
}
|
|
|
|
.underline {
|
|
transform: scaleX(1);
|
|
}
|
|
}
|
|
|
|
&.is-disabled {
|
|
opacity: 0.3;
|
|
pointer-events: none;
|
|
}
|
|
|
|
&.is-error {
|
|
.textarea-wrap textarea {
|
|
border-color: transparent;
|
|
}
|
|
|
|
.underline {
|
|
background: currentColor;
|
|
transform: scaleX(1);
|
|
}
|
|
|
|
.error-msg {
|
|
opacity: 0.65;
|
|
}
|
|
}
|
|
}
|
|
</style>
|