Skip to content

Commit

Permalink
<feat>(Textarea): add counter
Browse files Browse the repository at this point in the history
  • Loading branch information
maxsteinwand committed Nov 17, 2023
1 parent bcc46b8 commit 7b0ca94
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 23 deletions.
24 changes: 24 additions & 0 deletions docs/components/content/examples/TextareaExampleSlotCounter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script setup>
const input = ref('I love NuxtUI!')
const badgeColor = (remaining)=>{

Check failure on line 4 in docs/components/content/examples/TextareaExampleSlotCounter.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 18)

Missing space before =>

Check failure on line 4 in docs/components/content/examples/TextareaExampleSlotCounter.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 18)

Missing space after =>
switch (true) {
case remaining < 5: return 'red';

Check failure on line 6 in docs/components/content/examples/TextareaExampleSlotCounter.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 18)

Expected indentation of 2 spaces but found 4 spaces

Check failure on line 6 in docs/components/content/examples/TextareaExampleSlotCounter.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 18)

Extra semicolon
case remaining < 10: return 'amber';

Check failure on line 7 in docs/components/content/examples/TextareaExampleSlotCounter.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 18)

Expected indentation of 2 spaces but found 4 spaces

Check failure on line 7 in docs/components/content/examples/TextareaExampleSlotCounter.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 18)

Extra semicolon
case remaining < 15: return 'orange';

Check failure on line 8 in docs/components/content/examples/TextareaExampleSlotCounter.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 18)

Expected indentation of 2 spaces but found 4 spaces

Check failure on line 8 in docs/components/content/examples/TextareaExampleSlotCounter.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 18)

Extra semicolon
default: return 'green';

Check failure on line 9 in docs/components/content/examples/TextareaExampleSlotCounter.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 18)

Expected indentation of 2 spaces but found 4 spaces

Check failure on line 9 in docs/components/content/examples/TextareaExampleSlotCounter.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 18)

Extra semicolon
}
}
</script>

<template>
<UTextarea v-model="input" :counter="15">
<template #counter="{focused, letterCount, maxValue}">
<UBadge :color="badgeColor(maxValue - letterCount)">
{{ maxValue - letterCount }} Characters remaining!

Check warning on line 18 in docs/components/content/examples/TextareaExampleSlotCounter.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 18)

Multiple spaces found before '}}'

<UIcon class="ml-1" v-if="focused" name="i-heroicons-eye" />

Check warning on line 20 in docs/components/content/examples/TextareaExampleSlotCounter.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 18)

Attribute "v-if" should go before "class"
</UBadge>
</template>
</UTextarea>
</template>
25 changes: 25 additions & 0 deletions docs/content/3.forms/2.textarea.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,31 @@ props:
---
::

### Counter

Use the `counter` prop to enable the counter and to set a maximum value. The counter will show if you are focusing the textarea or when setting `persistent-counter`. This is only visual and not validating the input.

::component-card
---
baseProps:
modelValue: 'This is an example of the counter prop'
props:
counter: '540'
persistentCounter: false
excludedProps:
- counter

---
::

## Slots

### `counter`

You can use the `#counter` slot to show a custom counter. It receives `focused` `letterCount` and `maxValue` of the textarea.

:component-example{component="textarea-example-slot-counter"}

## Props

:component-props
Expand Down
44 changes: 42 additions & 2 deletions src/runtime/components/forms/Textarea.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@
@blur="onBlur"
@change="onChange"
/>
<slot name="counter" v-bind="{ focused, letterCount, maxValue }">
<span v-if="counterVisible" :class="counterClass">{{ maxValue ? `${letterCount} / ${maxValue}` :
String(letterCount) }}</span>
</slot>
</div>
</template>

<script lang="ts">
import { ref, computed, toRef, watch, onMounted, nextTick, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { useFocus } from '@vueuse/core'
import { twMerge, twJoin } from 'tailwind-merge'
import { defu } from 'defu'
import { useUI } from '../../composables/useUI'
Expand Down Expand Up @@ -125,6 +130,14 @@ export default defineComponent({
modelModifiers: {
type: Object as PropType<{ trim?: boolean, lazy?: boolean, number?: boolean }>,
default: () => ({})
},
counter: {
type: [Boolean, Number, String] as PropType<true | number | string>,
default: false
},
persistentCounter: {
type: Boolean,
default: false
}
},
emits: ['update:modelValue', 'blur'],
Expand All @@ -136,6 +149,21 @@ export default defineComponent({
const modelModifiers = ref(defu({}, props.modelModifiers, { trim: false, lazy: false, number: false }))
const textarea = ref<HTMLTextAreaElement | null>(null)
const { focused } = useFocus(textarea)
const counterVisible = computed(() => (props.counter && focused.value) || props.persistentCounter)
const maxValue = computed(() => {
if (
!props.counter ||
(typeof props.counter !== 'number' &&
typeof props.counter !== 'string')
) return undefined
return props.counter
})
const letterCount = ref(props.modelValue.toString().length)
const autoFocus = () => {
if (props.autofocus) {
Expand Down Expand Up @@ -175,13 +203,15 @@ export default defineComponent({
value = looseToNumber(value)
}
letterCount.value = value.length
emit('update:modelValue', value)
emitFormInput()
}
const onInput = (event: InputEvent) => {
autoResize()
if (!modelModifiers.value.lazy) {
if (!modelModifiers.value.lazy) {
updateInput((event.target as HTMLInputElement).value)
}
}
Expand Down Expand Up @@ -221,6 +251,11 @@ export default defineComponent({
}, 100)
})
const counterClass = computed(() => {
return twJoin(ui.value.counter.wrapper,
ui.value.size[size.value])
})
const textareaClass = computed(() => {
const variant = ui.value.color?.[color.value as string]?.[props.variant as string] || ui.value.variant[props.variant]
Expand All @@ -245,9 +280,14 @@ export default defineComponent({
textarea,
// eslint-disable-next-line vue/no-dupe-keys
textareaClass,
counterClass,
onInput,
onChange,
onBlur
onBlur,
focused,
counterVisible,
maxValue,
letterCount
}
}
})
Expand Down
25 changes: 4 additions & 21 deletions src/runtime/ui.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,9 @@ export const formGroup = {

export const textarea = {
...input,
counter: {
wrapper: 'absolute right-0'
},
default: {
size: 'sm',
color: 'white',
Expand Down Expand Up @@ -1124,7 +1127,7 @@ export const pagination = {
nextButton: {
color: 'white',
class: 'rtl:[&_span:last-child]:rotate-180',
icon: 'i-heroicons-chevron-right-20-solid'
icon: 'i-heroicons-chevron-right-20-solid '
}
}
}
Expand Down Expand Up @@ -1163,26 +1166,6 @@ export const tabs = {
}
}

export const breadcrumb = {
wrapper: 'relative',
ol: 'flex items-center gap-x-1.5',
li: 'flex items-center gap-x-1.5 text-gray-500 dark:text-gray-400 text-sm',
base: 'flex items-center gap-x-1.5 group font-semibold',
icon: {
base: 'flex-shrink-0 w-4 h-4',
active: '',
inactive: ''
},
divider: {
base: 'flex-shrink-0 w-5 h-5'
},
active: 'text-primary-500 dark:text-primary-400',
inactive: ' hover:text-gray-700 dark:hover:text-gray-200',
default: {
divider: 'i-heroicons-chevron-right-20-solid'
}
}

// Overlays

export const modal = {
Expand Down

0 comments on commit 7b0ca94

Please sign in to comment.