mirror of
https://github.com/HoshinoSuzumi/rayine-ui.git
synced 2025-04-07 12:48:50 +08:00
✨ feat(textarea): new component textarea
This commit is contained in:
parent
77cc38e552
commit
00a7c05aec
182
docs/content/2.components/textarea.md
Normal file
182
docs/content/2.components/textarea.md
Normal file
@ -0,0 +1,182 @@
|
||||
---
|
||||
description: Create a textarea component
|
||||
since: 1.3.5
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
The basic usage.
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
privateProps:
|
||||
placeholder: Description
|
||||
---
|
||||
::
|
||||
|
||||
### Sizes
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
privateProps:
|
||||
placeholder: Description
|
||||
props:
|
||||
size: sm
|
||||
---
|
||||
::
|
||||
|
||||
### Colors
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
privateProps:
|
||||
placeholder: Description
|
||||
props:
|
||||
color: primary
|
||||
---
|
||||
::
|
||||
|
||||
### Variants
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
privateProps:
|
||||
placeholder: Description
|
||||
props:
|
||||
variant: outline
|
||||
---
|
||||
::
|
||||
|
||||
### Placeholder
|
||||
|
||||
You can also set a placeholder.
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
props:
|
||||
placeholder: "Description here..."
|
||||
---
|
||||
::
|
||||
|
||||
### Padded
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
privateProps:
|
||||
placeholder: Description
|
||||
variant: plain
|
||||
props:
|
||||
padded: false
|
||||
---
|
||||
::
|
||||
|
||||
### Rows
|
||||
|
||||
Set the number of rows of the textarea.
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
privateProps:
|
||||
placeholder: Description
|
||||
props:
|
||||
rows: 4
|
||||
---
|
||||
::
|
||||
|
||||
### Resize
|
||||
|
||||
Enable the resize control.
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
privateProps:
|
||||
placeholder: Description
|
||||
props:
|
||||
resize: true
|
||||
---
|
||||
::
|
||||
|
||||
### Auto Resize
|
||||
|
||||
The `autosize` prop enables the auto resizing of the textarea. The textarea will grow in height as the user types.
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
privateProps:
|
||||
placeholder: Description
|
||||
props:
|
||||
autosize: true
|
||||
---
|
||||
::
|
||||
|
||||
The `maxrows` prop can be used to set the maximum number of rows the textarea can grow to.
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
privateProps:
|
||||
placeholder: Description
|
||||
props:
|
||||
autosize: true
|
||||
maxrows: 8
|
||||
---
|
||||
::
|
||||
|
||||
### Disabled
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
privateProps:
|
||||
placeholder: Description
|
||||
props:
|
||||
disabled: true
|
||||
---
|
||||
::
|
||||
|
||||
### Model Modifiers
|
||||
|
||||
#### .trim
|
||||
|
||||
The `.trim` modifier trims the input value.
|
||||
|
||||
```vue [page]
|
||||
<script lang="ts" setup>
|
||||
const modal = ref<string>("");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RayTextarea v-model.trim="modal" />
|
||||
</template>
|
||||
```
|
||||
|
||||
#### .number
|
||||
|
||||
The `.number` modifier converts the input value to a number. Non-numeric values are ignored.
|
||||
|
||||
```vue [page]
|
||||
<script lang="ts" setup>
|
||||
const modal = ref<number>(0);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RayTextarea v-model.number="modal" />
|
||||
</template>
|
||||
```
|
||||
|
||||
#### .lazy
|
||||
|
||||
The `.lazy` modifier syncs the input value with the model only on `change` event.
|
||||
|
||||
```vue [page]
|
||||
<script lang="ts" setup>
|
||||
const modal = ref<string>("");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RayTextarea v-model.lazy="modal" />
|
||||
</template>
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
::ComponentDefaults
|
||||
::
|
192
src/runtime/components/forms/Textarea.vue
Normal file
192
src/runtime/components/forms/Textarea.vue
Normal file
@ -0,0 +1,192 @@
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, ref, toRef, watch, type PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import defu from 'defu'
|
||||
import { textarea } from '../../ui.config'
|
||||
import type { DeepPartial, Strategy, TextareaColor, TextareaModelModifiers, TextareaSize, TextareaVariant } from '../../types'
|
||||
import { useRayUI } from '#build/imports'
|
||||
|
||||
const config = textarea
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [String, Number] as PropType<string | number | null>,
|
||||
default: '',
|
||||
},
|
||||
size: {
|
||||
type: String as PropType<TextareaSize>,
|
||||
default: config.default.size,
|
||||
},
|
||||
variant: {
|
||||
type: String as PropType<TextareaVariant>,
|
||||
default: config.default.variant,
|
||||
},
|
||||
color: {
|
||||
type: String as PropType<TextareaColor>,
|
||||
default: config.default.color,
|
||||
},
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
autofocusDelay: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
rows: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
},
|
||||
autosize: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
maxrows: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
padded: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
resize: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
class: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
|
||||
default: () => ({}),
|
||||
},
|
||||
modelModifiers: {
|
||||
type: Object as PropType<TextareaModelModifiers>,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'blur',
|
||||
'change',
|
||||
],
|
||||
setup(props, { emit }) {
|
||||
const { ui, attrs } = useRayUI('textarea', toRef(props, 'ui'), config)
|
||||
const modelModifiers = ref(defu({}, props.modelModifiers, { lazy: false, number: false, trim: false }))
|
||||
|
||||
const textarea = ref<HTMLTextAreaElement | null>(null)
|
||||
|
||||
const baseClass = computed(() => {
|
||||
return twMerge(twJoin(
|
||||
ui.value.base,
|
||||
ui.value.rounded,
|
||||
ui.value.placeholder,
|
||||
ui.value.size[props.size],
|
||||
props.padded && ui.value.padding[props.size],
|
||||
ui.value.variant[props.variant].replaceAll('{color}', props.color),
|
||||
!props.resize && 'resize-none',
|
||||
), props.class)
|
||||
})
|
||||
|
||||
const autoResize = () => {
|
||||
if (!props.autosize) return
|
||||
if (!textarea.value) return
|
||||
textarea.value.rows = props.rows
|
||||
const overflowBefore = textarea.value.style.overflow
|
||||
textarea.value.style.overflow = 'hidden'
|
||||
|
||||
const style = window.getComputedStyle(textarea.value)
|
||||
const padding = Number.parseInt(style.paddingTop) + Number.parseInt(style.paddingBottom)
|
||||
const lineHeight = Number.parseInt(style.lineHeight)
|
||||
const { scrollHeight, clientHeight } = textarea.value
|
||||
const computedRows = Math.floor((scrollHeight - padding) / lineHeight)
|
||||
if (computedRows > props.rows) {
|
||||
textarea.value.rows = props.maxrows ? Math.min(computedRows, props.maxrows) : computedRows
|
||||
}
|
||||
textarea.value.style.overflow = overflowBefore
|
||||
|
||||
console.log('computedRows', computedRows)
|
||||
}
|
||||
|
||||
const updateValue = (value: string) => {
|
||||
if (modelModifiers.value.trim) {
|
||||
value = value.trim()
|
||||
}
|
||||
|
||||
if (modelModifiers.value.number) {
|
||||
const n = Number.parseFloat(value)
|
||||
value = (Number.isNaN(n) ? value : n) as any
|
||||
}
|
||||
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
|
||||
const onInput = (e: Event) => {
|
||||
autoResize()
|
||||
if (modelModifiers.value.lazy) return
|
||||
updateValue((e.target as HTMLInputElement).value)
|
||||
}
|
||||
|
||||
const onChange = (e: Event) => {
|
||||
const value = (e.target as HTMLInputElement).value
|
||||
emit('change', value)
|
||||
if (modelModifiers.value.lazy) {
|
||||
updateValue(value)
|
||||
}
|
||||
if (modelModifiers.value.trim) {
|
||||
(e.target as HTMLInputElement).value = value.trim()
|
||||
}
|
||||
}
|
||||
|
||||
const onBlur = (e: Event) => {
|
||||
emit('blur', e)
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, () => {
|
||||
autoResize()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (props.autofocus) {
|
||||
setTimeout(() => {
|
||||
textarea.value?.focus()
|
||||
}, props.autofocusDelay)
|
||||
}
|
||||
autoResize()
|
||||
})
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
attrs,
|
||||
textarea,
|
||||
baseClass,
|
||||
onInput,
|
||||
onChange,
|
||||
onBlur,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="ui.wrapper">
|
||||
<textarea
|
||||
ref="textarea"
|
||||
:class="baseClass"
|
||||
:rows="rows"
|
||||
:placeholder="placeholder"
|
||||
v-bind="attrs"
|
||||
:value="modelValue"
|
||||
@input="onInput"
|
||||
@change="onChange"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
@ -1,6 +1,7 @@
|
||||
export * from './button'
|
||||
export * from './message'
|
||||
export * from './input'
|
||||
export * from './textarea'
|
||||
export * from './kbd'
|
||||
export * from './toggle'
|
||||
|
||||
|
19
src/runtime/types/textarea.d.ts
vendored
Normal file
19
src/runtime/types/textarea.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import type { textarea } from '../ui.config'
|
||||
import type { ExtractDeepKey } from './utils'
|
||||
import type colors from '#ray-colors'
|
||||
|
||||
export type TextareaSize =
|
||||
| keyof typeof textarea.size
|
||||
| ExtractDeepKey<AppConfig, ['rayui', 'textarea', 'size']>
|
||||
export type TextareaColor =
|
||||
| ExtractDeepKey<AppConfig, ['rayui', 'textarea', 'color']>
|
||||
| (typeof colors)[number]
|
||||
export type TextareaVariant =
|
||||
| keyof typeof textarea.variant
|
||||
| ExtractDeepKey<AppConfig, ['rayui', 'textarea', 'variant']>
|
||||
export type TextareaModelModifiers = {
|
||||
number?: boolean
|
||||
trim?: boolean
|
||||
lazy?: boolean
|
||||
}
|
24
src/runtime/ui.config/forms/textarea.ts
Normal file
24
src/runtime/ui.config/forms/textarea.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { standard } from '..'
|
||||
|
||||
export default {
|
||||
wrapper: 'relative',
|
||||
base: 'relative w-full block focus:outline-none disabled:cursor-not-allowed disabled:opacity-70 transition',
|
||||
placeholder: 'placeholder:text-gray-400 dark:placeholder:text-gray-500',
|
||||
rounded: 'rounded-md',
|
||||
size: {
|
||||
...standard.size,
|
||||
},
|
||||
padding: {
|
||||
...standard.padding,
|
||||
},
|
||||
variant: {
|
||||
outline:
|
||||
'shadow-sm bg-transparent text-gray-900 dark:text-white ring ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-{color}-500 dark:focus:ring-{color}-400',
|
||||
plain: 'bg-transparent',
|
||||
},
|
||||
default: {
|
||||
size: 'sm',
|
||||
color: 'primary',
|
||||
variant: 'outline',
|
||||
},
|
||||
}
|
@ -7,6 +7,7 @@ export { default as kbd } from './elements/kbd'
|
||||
|
||||
// forms
|
||||
export { default as input } from './forms/input'
|
||||
export { default as textarea } from './forms/textarea'
|
||||
export { default as toggle } from './forms/toggle'
|
||||
|
||||
// overlays
|
||||
|
Loading…
Reference in New Issue
Block a user