Skip to content

Commit

Permalink
feat: textarea
Browse files Browse the repository at this point in the history
  • Loading branch information
jaskang committed Apr 21, 2024
1 parent a8966db commit c102c88
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 51 deletions.
1 change: 1 addition & 0 deletions site/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default defineConfigWithTheme<ThemeConfig>({
{ title: 'Checkbox', link: '/components/checkbox' },
{ title: 'Radio', link: '/components/radio' },
{ title: 'Input', link: '/components/input' },
{ title: 'Textarea', link: '/components/textarea' },
{ title: 'Select', link: '/components/select' },
{ title: 'Switch', link: '/components/switch' },
],
Expand Down
53 changes: 53 additions & 0 deletions site/components/textarea.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Textarea

## 基础用法

```vue demo
<script setup>
import { ref } from 'vue'
</script>
<template>
<Textarea placeholder="请输入内容" />
</template>
```

## 工具条

```vue demo
<script setup>
import {
PaperAirplaneIcon,
CodeBracketIcon,
TrashIcon,
ArchiveBoxIcon,
CircleStackIcon,
} from '@heroicons/vue/24/outline'
import { ref } from 'vue'
</script>
<template>
<Textarea placeholder="请输入内容">
<template #toolbar>
<div class="flex items-center justify-between">
<div>
<Button variant="ghost">
<template #icon>
<CodeBracketIcon />
</template>
</Button>
<Button variant="ghost">
<template #icon>
<PaperAirplaneIcon />
</template>
</Button>
</div>
<Button color="primary">
<template #icon>
<PaperAirplaneIcon />
</template>
</Button>
</div>
</template>
</Textarea>
</template>
```
38 changes: 17 additions & 21 deletions src/Base/BaseInput/BaseInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@ import { type PropType } from 'vue'
defineOptions({ name: 'BaseInput' })
const borderMap = {
default: 'ring-slate-300 dark:ring-slate-600',
success: 'ring-success-500',
warning: 'ring-warning-500',
danger: 'ring-danger-500',
default: 'outline-slate-300 dark:outline-slate-600',
success: 'outline-success-500',
warning: 'outline-warning-500',
danger: 'outline-danger-500',
}
const focusBorderMap = {
default: 'focus-within:ring-primary-500 dark:focus-within:ring-primary-500',
success: 'focus-within:ring-success-500 dark:focus-within:ring-success-500',
warning: 'focus-within:ring-warning-500 dark:focus-within:ring-warning-500',
danger: 'focus-within:ring-danger-500 dark:focus-within:ring-danger-500',
const focusWithinMap = {
default: 'focus-within:outline-primary-500 dark:focus-within:outline-primary-500',
success: 'focus-within:outline-success-500 dark:focus-within:outline-success-500',
warning: 'focus-within:outline-warning-500 dark:focus-within:outline-warning-500',
danger: 'focus-within:outline-danger-500 dark:focus-within:outline-danger-500',
}
const focusedBorderMap = {
default: 'data-[focused=true]:ring-primary-500 dark:data-[focused=true]:ring-primary-500',
success: 'data-[focused=true]:ring-success-500 dark:data-[focused=true]:ring-success-500',
warning: 'data-[focused=true]:ring-warning-500 dark:data-[focused=true]:ring-warning-500',
danger: 'data-[focused=true]:ring-danger-500 dark:data-[focused=true]:ring-danger-500',
const focusMap = {
default: 'data-[focused=true]:outline-primary-500 dark:data-[focused=true]:outline-primary-500',
success: 'data-[focused=true]:outline-success-500 dark:data-[focused=true]:outline-success-500',
warning: 'data-[focused=true]:outline-warning-500 dark:data-[focused=true]:outline-warning-500',
danger: 'data-[focused=true]:outline-danger-500 dark:data-[focused=true]:outline-danger-500',
}
const props = defineProps({
Expand All @@ -37,14 +37,10 @@ const props = defineProps({
:disabled="disabled"
:data-focused="focused"
:class="[
`z-ring-input flex items-center rounded-md text-sm shadow-sm ring-1 ring-inset transition-all
focus-within:z-10 focus-within:ring-2
data-[focused=true]:z-10 data-[focused=true]:ring-2
${focusedBorderMap[status]}
${focusBorderMap[status]}`,
`z-base-input relative rounded-md text-sm shadow-sm outline outline-1 -outline-offset-1 transition-all`,
props.disabled
? 'cursor-not-allowed bg-slate-50 text-slate-500 opacity-50 ring-slate-300 dark:bg-slate-700 dark:ring-slate-600'
: `cursor-pointer bg-white dark:bg-slate-800 ${borderMap[status]}`,
? 'cursor-not-allowed bg-slate-50 text-slate-500 opacity-50 outline-slate-300 dark:bg-slate-700 dark:outline-slate-600'
: `cursor-pointer bg-white focus-within:z-10 focus-within:outline-2 data-[focused=true]:z-10 data-[focused=true]:outline-2 dark:bg-slate-800 ${borderMap[status]} ${focusMap[status]} ${focusWithinMap[status]}`,
]"
>
<slot />
Expand Down
29 changes: 5 additions & 24 deletions src/Input/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const props = defineProps({
allowClear: Boolean,
})
const focused = ref(false)
const [modelValue, setModelValue] = useModelValue(props, {
onChange: (v: string) => {
emit('change', v)
Expand All @@ -34,24 +33,6 @@ const onInput = (e: Event) => {
setModelValue(el.value)
emit('input', e)
}
const onFocus = (e: FocusEvent) => {
if (props.disabled) {
e.preventDefault()
return false
} else {
focused.value = true
emit('focus', e)
}
}
const onBlur = (e: FocusEvent) => {
if (props.disabled) {
e.preventDefault()
return false
} else {
focused.value = false
emit('blur', e)
}
}
const inputRef = ref<HTMLInputElement>()
Expand All @@ -61,7 +42,7 @@ defineExpose({
})
</script>
<template>
<BaseInput :disabled="disabled">
<BaseInput :disabled="disabled" class="flex items-center">
<span v-if="prefix || slots.prefix" class="z-input_prefix flex h-full items-center text-slate-500">
<slot name="prefix">
<span class="pl-3">
Expand All @@ -71,7 +52,7 @@ defineExpose({
</span>
<input
ref="inputRef"
class="z-input_input disabled block w-full flex-1 cursor-[inherit] border-none bg-transparent px-3 py-1.5 text-sm leading-6 outline-none placeholder:text-slate-400 focus:outline-none"
class="z-input_input block w-full flex-1 cursor-[inherit] border-none bg-transparent px-3 py-1.5 text-sm leading-6 outline-none placeholder:text-slate-400 focus:outline-none"
:class="{
'pl-1': prefix || slots.prefix,
'pr-1': suffix || slots.suffix,
Expand All @@ -82,10 +63,10 @@ defineExpose({
:readonly="readonly"
:disabled="disabled"
:placeholder="placeholder"
:onInput="onInput"
:onFocus="onFocus"
:onBlur="onBlur"
autocomplete="off"
@input="onInput"
@focus="emit('focus', $event)"
@blur="emit('blur', $event)"
/>
<span v-if="suffix || slots.suffix" class="z-input_suffix flex h-full items-center text-slate-500">
<slot name="suffix">
Expand Down
12 changes: 6 additions & 6 deletions src/Tab/Tabs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ const clickHandler = (key: string) => {
}
</script>
<template>
<div v-if="items.length > 0" class="border-b border-slate-200 dark:border-slate-700">
<nav class="flex space-x-1">
<div v-if="items.length > 0">
<nav class="flex space-x-1 border-b border-slate-200 dark:border-slate-700">
<button
v-for="item in props.items"
type="button"
Expand All @@ -42,10 +42,10 @@ const clickHandler = (key: string) => {
{{ item.label }}
</button>
</nav>
<div>
<template v-if="keys.includes(currentKey)">
<template v-if="keys.includes(currentKey)">
<div>
<slot name="default" :current="currentKey" />
</template>
</div>
</div>
</template>
</div>
</template>
60 changes: 60 additions & 0 deletions src/Textarea/Textarea.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useModelValue } from '../use/useModelValue'
import { BaseInput } from '@/Base'
import { scrollbarClass } from '@/utils/tw'
defineOptions({ name: 'Textarea' })
const emit = defineEmits<{
'update:value': [string]
change: [string]
input: [Event]
focus: [FocusEvent]
blur: [FocusEvent]
}>()
const slots = defineSlots<{ toolbar?(props: { content: string }): any }>()
const props = defineProps({
value: String,
placeholder: String,
readonly: Boolean,
disabled: Boolean,
})
const [modelValue, setModelValue] = useModelValue(props, {
onChange: (v: string) => {
emit('change', v)
},
})
const onInput = (e: Event) => {
const el = e.currentTarget as HTMLInputElement
setModelValue(el.value)
emit('input', e)
}
const inputRef = ref<HTMLTextAreaElement>()
</script>
<template>
<BaseInput class="z-textarea relative flex-col">
<textarea
class="block w-full flex-1 cursor-[inherit] border-none bg-transparent px-3 py-1.5 text-sm placeholder:text-slate-400"
:class="scrollbarClass"
style="box-shadow: none"
rows="3"
ref="inputRef"
type="text"
:value="modelValue"
:readonly="readonly"
:disabled="disabled"
:placeholder="placeholder"
autocomplete="off"
@input="onInput"
@focus="emit('focus', $event)"
@blur="emit('blur', $event)"
/>
<div v-if="slots.toolbar" class="inset-x-px bottom-px block rounded-b-md bg-white p-2 dark:bg-slate-900">
<slot name="toolbar" :content="modelValue"></slot>
</div>
</BaseInput>
</template>
<style></style>
1 change: 1 addition & 0 deletions src/Textarea/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Textarea } from './Textarea.vue'
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { default as Popover } from './Popover/index.vue'
import { default as Tooltip } from './Tooltip/index.vue'
import { default as SpaceCompact } from './Space/SpaceCompact.vue'
import { Tabs } from './Tab'
import { Textarea } from './Textarea'

export {
// components
Expand All @@ -31,6 +32,7 @@ export {
Tooltip,
SpaceCompact,
Tabs,
Textarea,
// use
useAnchor,
}
Expand All @@ -53,5 +55,6 @@ export const plugin: Plugin = {
app.component(Tooltip.name!, Tooltip)
app.component(SpaceCompact.name!, SpaceCompact)
app.component(Tabs.name!, Tabs)
app.component(Textarea.name!, Textarea)
},
}
3 changes: 3 additions & 0 deletions src/utils/tw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,6 @@ export function tw<T extends VariantsObject>(
return clsx(cls)
}
}

export const scrollbarClass =
'[&::-webkit-scrollbar-sizer] [&::-webkit-scrollbar-button]:h-0 [&::-webkit-scrollbar-button]:w-0 [&::-webkit-scrollbar-thumb]:rounded [&::-webkit-scrollbar-thumb]:bg-slate-300 dark:[&::-webkit-scrollbar-thumb]:bg-slate-500/50 [&::-webkit-scrollbar-track]:rounded [&::-webkit-scrollbar-track]:bg-slate-100 dark:[&::-webkit-scrollbar-track]:bg-slate-500/[0.16] [&::-webkit-scrollbar]:h-2 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar]:bg-transparent'

0 comments on commit c102c88

Please sign in to comment.