feat(ui): add label prop and slot to textarea component

This commit is contained in:
upsiflu 2024-12-29 18:31:18 +01:00
parent 9e3f2cfb57
commit b2ba7e1e6a
3 changed files with 73 additions and 29 deletions

View File

@ -4,8 +4,9 @@ import { useTextareaAutosize, computedWithControl, useManualRefHistory, watchDeb
import Button from './Button.vue' import Button from './Button.vue'
import Markdown from './Markdown.vue' import Markdown from './Markdown.vue'
import Layout from '~/components/ui/Layout.vue'
const { max=Infinity, placeholder='' } = defineProps<{ max?:number,placeholder?:string }>() const { max=Infinity, placeholder='', ...restProps } = defineProps<{ max?:number,placeholder?:string, label?:string }>()
const model = defineModel<string>({ default: '' }) const model = defineModel<string>({ default: '' })
@ -177,38 +178,48 @@ const focus = () => textarea.value.focus()
</script> </script>
<template> <template>
<div :class="{ 'has-preview': preview }" class="funkwhale textarea" @mousedown.prevent="focus" @mouseup.prevent="focus"> <Layout stack no-gap label
<Markdown :md="model" class="preview" /> class="funkwhale textarea-label"
<textarea ref="textarea" @click="updateLineNumber" @mousedown.stop @mouseup.stop @keydown.left="updateLineNumber" >
@keydown.right="updateLineNumber" @keydown.up="updateLineNumber" @keydown.down="updateLineNumber" <span v-if="$slots['label']" class="label">
@keydown.enter="newline" @keydown.ctrl.shift.z.exact.prevent="redo" @keydown.ctrl.z.exact.prevent="undo" <slot name="label" />
@keydown.ctrl.b.exact.prevent="bold" @keydown.ctrl.i.exact.prevent="italics" </span>
@keydown.ctrl.shift.x.exact.prevent="strikethrough" @keydown.ctrl.k.exact.prevent="link" :maxlength="max" <span v-if="restProps.label" class="label">
:placeholder="placeholder" v-model="model" id="textarea_id" /> {{ restProps.label }}
<div class="textarea-buttons"> </span>
<Button @click="preview = !preview" icon="bi-eye" color="secondary" :aria-pressed="preview || undefined" /> <div :class="{ 'has-preview': preview }" class="funkwhale textarea" @mousedown.prevent="focus" @mouseup.prevent="focus">
<Markdown :md="model" class="preview" />
<textarea ref="textarea" @click="updateLineNumber" @mousedown.stop @mouseup.stop @keydown.left="updateLineNumber"
@keydown.right="updateLineNumber" @keydown.up="updateLineNumber" @keydown.down="updateLineNumber"
@keydown.enter="newline" @keydown.ctrl.shift.z.exact.prevent="redo" @keydown.ctrl.z.exact.prevent="undo"
@keydown.ctrl.b.exact.prevent="bold" @keydown.ctrl.i.exact.prevent="italics"
@keydown.ctrl.shift.x.exact.prevent="strikethrough" @keydown.ctrl.k.exact.prevent="link" :maxlength="max"
:placeholder="placeholder" v-model="model" id="textarea_id" />
<div class="textarea-buttons">
<Button @click="preview = !preview" icon="bi-eye" color="secondary" :aria-pressed="preview || undefined" />
<div class="separator" /> <div class="separator" />
<Button @click="heading1" icon="bi-type-h1" color="secondary" :aria-pressed="isHeading1 || undefined" :disabled="preview" /> <Button @click="heading1" icon="bi-type-h1" color="secondary" :aria-pressed="isHeading1 || undefined" :disabled="preview" />
<Button @click="heading2" icon="bi-type-h2" color="secondary" :aria-pressed="isHeading2 || undefined" :disabled="preview" /> <Button @click="heading2" icon="bi-type-h2" color="secondary" :aria-pressed="isHeading2 || undefined" :disabled="preview" />
<Button @click="paragraph" icon="bi-paragraph" color="secondary" :aria-pressed="isParagraph || undefined" :disabled="preview" /> <Button @click="paragraph" icon="bi-paragraph" color="secondary" :aria-pressed="isParagraph || undefined" :disabled="preview" />
<Button @click="quote" icon="bi-quote" color="secondary" :aria-pressed="isQuote || undefined" :disabled="preview" /> <Button @click="quote" icon="bi-quote" color="secondary" :aria-pressed="isQuote || undefined" :disabled="preview" />
<Button @click="orderedList" icon="bi-list-ol" color="secondary" :aria-pressed="isOrderedList || undefined" <Button @click="orderedList" icon="bi-list-ol" color="secondary" :aria-pressed="isOrderedList || undefined"
:disabled="preview" /> :disabled="preview" />
<Button @click="unorderedList" icon="bi-list-ul" color="secondary" :aria-pressed="isUnorderedList || undefined" <Button @click="unorderedList" icon="bi-list-ul" color="secondary" :aria-pressed="isUnorderedList || undefined"
:disabled="preview" /> :disabled="preview" />
<div class="separator" /> <div class="separator" />
<Button @click="bold" icon="bi-type-bold" color="secondary" :disabled="preview" /> <Button @click="bold" icon="bi-type-bold" color="secondary" :disabled="preview" />
<Button @click="italics" icon="bi-type-italic" color="secondary" :disabled="preview" /> <Button @click="italics" icon="bi-type-italic" color="secondary" :disabled="preview" />
<Button @click="strikethrough" icon="bi-type-strikethrough" color="secondary" :disabled="preview" /> <Button @click="strikethrough" icon="bi-type-strikethrough" color="secondary" :disabled="preview" />
<Button @click="link" icon="bi-link-45deg" color="secondary" :disabled="preview" /> <Button @click="link" icon="bi-link-45deg" color="secondary" :disabled="preview" />
<span v-if="max !== Infinity && typeof max === 'number'" class="letter-count">{{ max - model.length }}</span> <span v-if="max !== Infinity && typeof max === 'number'" class="letter-count">{{ max - model.length }}</span>
</div>
</div> </div>
</div> </Layout>
</template> </template>
<style lang="scss"> <style lang="scss">

View File

@ -1,4 +1,12 @@
.funkwhale { .funkwhale {
&.textarea-label {
display: block;
> .label {
padding-bottom: 4px;
font-size:14px;
font-weight:600;
}
}
&.textarea { &.textarea {
background-color: var(--fw-bg-color); background-color: var(--fw-bg-color);
box-shadow: inset 0 0 0 4px var(--fw-border-color); box-shadow: inset 0 0 0 4px var(--fw-border-color);

View File

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import Textarea from '~/components/ui/Textarea.vue' import Textarea from '~/components/ui/Textarea.vue'
import { ref } from 'vue' import { ref } from 'vue'
const text1 = ref('# Funk\nwhale') const text1 = ref('# Funk\nwhale')
@ -27,12 +28,12 @@ Create a textarea and attach its input to a value using a `v-model` directive.
```vue-html{2} ```vue-html{2}
<Textarea <Textarea
v-model="text" v-model="text" label="My Area"
/> />
``` ```
<ClientOnly> <ClientOnly>
<Textarea v-model="text1" /> <Textarea v-model="text1" label="My Area"/>
</ClientOnly> </ClientOnly>
## Textarea max length ## Textarea max length
@ -64,3 +65,27 @@ Add a placeholder to a textarea to guide users on what they should enter by pass
<ClientOnly> <ClientOnly>
<Textarea v-model="text3" placeholder="Describe this track here…" /> <Textarea v-model="text3" placeholder="Describe this track here…" />
</ClientOnly> </ClientOnly>
## Label slot
```vue-html{2-4}
<Textarea>
<template #label>
About my music
</template>
</Textarea>
```
<Textarea>
<template #label>
About my music <span style="color:red; float:right;">*required</span>
</template>
</Textarea>
If you just have a string, we have a convenience prop, so instead you can write:
```vue-html
<Textarea label="About my music" />
```
<Textarea label="About my music" />