mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-19 06:43:51 +02:00
replace some el-input with InputGroup
This commit is contained in:
@@ -10,18 +10,16 @@
|
||||
<span>{{ t('dialog.edit_send_invite_message.description') }}</span>
|
||||
</div>
|
||||
|
||||
<el-input
|
||||
<InputGroupCharCount
|
||||
v-model="editAndSendInviteDialog.newMessage"
|
||||
type="textarea"
|
||||
size="small"
|
||||
maxlength="64"
|
||||
show-word-limit
|
||||
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||
placeholder=""
|
||||
style="margin-top: 10px"></el-input>
|
||||
:maxlength="64"
|
||||
multiline
|
||||
rows="2"
|
||||
class="mt-2.5"
|
||||
placeholder="" />
|
||||
|
||||
<template #footer>
|
||||
<Button variant="secondary" @click="cancelEditAndSendInvite">
|
||||
<Button variant="secondary" class="mr-2" @click="cancelEditAndSendInvite">
|
||||
{{ t('dialog.edit_send_invite_message.cancel') }}
|
||||
</Button>
|
||||
<Button @click="saveEditAndSendInvite" :disabled="!editAndSendInviteDialog.newMessage">
|
||||
@@ -33,6 +31,7 @@
|
||||
|
||||
<script setup>
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InputGroupCharCount } from '@/components/ui/input-group';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -6,16 +6,13 @@
|
||||
width="600px"
|
||||
append-to-body>
|
||||
<div v-loading="bioDialog.loading">
|
||||
<el-input
|
||||
<InputGroupCharCount
|
||||
v-model="bioDialog.bio"
|
||||
type="textarea"
|
||||
size="small"
|
||||
maxlength="512"
|
||||
show-word-limit
|
||||
:autosize="{ minRows: 5, maxRows: 20 }"
|
||||
:maxlength="512"
|
||||
multiline
|
||||
rows="5"
|
||||
:placeholder="t('dialog.bio.bio_placeholder')"
|
||||
style="margin-bottom: 10px">
|
||||
</el-input>
|
||||
class="mb-2.5" />
|
||||
|
||||
<el-input
|
||||
v-for="(link, index) in bioDialog.bioLinks"
|
||||
@@ -48,6 +45,7 @@
|
||||
|
||||
<script setup>
|
||||
import { Delete } from '@element-plus/icons-vue';
|
||||
import { InputGroupCharCount } from '@/components/ui/input-group';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
|
||||
@@ -11,17 +11,13 @@
|
||||
<template v-if="!hideUserNotes || (hideUserNotes && hideUserMemos)">
|
||||
<span class="name">{{ t('dialog.user.info.note') }}</span>
|
||||
<br />
|
||||
<el-input
|
||||
<InputGroupCharCount
|
||||
v-model="note"
|
||||
class="extra"
|
||||
type="textarea"
|
||||
maxlength="256"
|
||||
show-word-limit
|
||||
:rows="6"
|
||||
:autosize="{ minRows: 2, maxRows: 20 }"
|
||||
:maxlength="256"
|
||||
multiline
|
||||
rows="6"
|
||||
:placeholder="t('dialog.user.info.note_placeholder')"
|
||||
size="small"
|
||||
resize="none"></el-input>
|
||||
input-class="extra resize-none" />
|
||||
</template>
|
||||
<template v-if="!hideUserMemos || (hideUserNotes && hideUserMemos)">
|
||||
<span class="name">{{ t('dialog.user.info.memo') }}</span>
|
||||
@@ -47,6 +43,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { InputGroupCharCount } from '@/components/ui/input-group';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
|
||||
@@ -6,15 +6,12 @@
|
||||
width="600px"
|
||||
append-to-body>
|
||||
<div v-loading="pronounsDialog.loading">
|
||||
<el-input
|
||||
type="textarea"
|
||||
<InputGroupCharCount
|
||||
v-model="pronounsDialog.pronouns"
|
||||
size="small"
|
||||
maxlength="32"
|
||||
show-word-limit
|
||||
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||
:placeholder="t('dialog.pronouns.pronouns_placeholder')">
|
||||
</el-input>
|
||||
:maxlength="32"
|
||||
multiline
|
||||
rows="2"
|
||||
:placeholder="t('dialog.pronouns.pronouns_placeholder')" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" :disabled="pronounsDialog.loading" @click="savePronouns">
|
||||
@@ -25,6 +22,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { InputGroupCharCount } from '@/components/ui/input-group';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
|
||||
@@ -41,13 +41,12 @@
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<el-input
|
||||
<InputGroupCharCount
|
||||
v-model="socialStatusDialog.statusDescription"
|
||||
:placeholder="t('dialog.social_status.status_placeholder')"
|
||||
maxlength="32"
|
||||
show-word-limit
|
||||
:maxlength="32"
|
||||
clearable
|
||||
style="margin-top: 10px"></el-input>
|
||||
class="mt-2.5" />
|
||||
<Collapsible v-model:open="isOpen" class="mt-3 flex w-full flex-col gap-2">
|
||||
<div class="flex items-center justify-between gap-4 px-4">
|
||||
<h4 class="text-sm font-semibold">{{ t('dialog.social_status.history') }}</h4>
|
||||
@@ -90,6 +89,7 @@
|
||||
import { computed, ref } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ChevronsUpDown } from 'lucide-vue-next';
|
||||
import { InputGroupCharCount } from '@/components/ui/input-group';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
35
src/components/ui/input-group/InputGroup.vue
Normal file
35
src/components/ui/input-group/InputGroup.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false }
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
data-slot="input-group"
|
||||
role="group"
|
||||
:class="
|
||||
cn(
|
||||
'group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-md border shadow-xs transition-[color,box-shadow] outline-none',
|
||||
'h-9 min-w-0 has-[>textarea]:h-auto',
|
||||
|
||||
// Variants based on alignment.
|
||||
'has-[>[data-align=inline-start]]:[&>input]:pl-2',
|
||||
'has-[>[data-align=inline-end]]:[&>input]:pr-2',
|
||||
'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3',
|
||||
'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3',
|
||||
|
||||
// Focus state.
|
||||
'has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]',
|
||||
|
||||
// Error state.
|
||||
'has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40',
|
||||
|
||||
props.class
|
||||
)
|
||||
">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
32
src/components/ui/input-group/InputGroupAddon.vue
Normal file
32
src/components/ui/input-group/InputGroupAddon.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script setup>
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
import { inputGroupAddonVariants } from '.';
|
||||
|
||||
const props = defineProps({
|
||||
align: { type: null, required: false, default: 'inline-start' },
|
||||
class: { type: null, required: false }
|
||||
});
|
||||
|
||||
function handleInputGroupAddonClick(e) {
|
||||
const currentTarget = e.currentTarget;
|
||||
const target = e.target;
|
||||
if (target && target.closest('button')) {
|
||||
return;
|
||||
}
|
||||
if (currentTarget && currentTarget?.parentElement) {
|
||||
currentTarget.parentElement?.querySelector('input')?.focus();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
role="group"
|
||||
data-slot="input-group-addon"
|
||||
:data-align="props.align"
|
||||
:class="cn(inputGroupAddonVariants({ align: props.align }), props.class)"
|
||||
@click="handleInputGroupAddonClick">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
21
src/components/ui/input-group/InputGroupButton.vue
Normal file
21
src/components/ui/input-group/InputGroupButton.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script setup>
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
import { inputGroupButtonVariants } from '.';
|
||||
|
||||
const props = defineProps({
|
||||
size: { type: null, required: false, default: 'xs' },
|
||||
variant: { type: null, required: false, default: 'ghost' },
|
||||
class: { type: null, required: false }
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button
|
||||
:data-size="props.size"
|
||||
:variant="props.variant"
|
||||
:class="cn(inputGroupButtonVariants({ size: props.size }), props.class)">
|
||||
<slot />
|
||||
</Button>
|
||||
</template>
|
||||
81
src/components/ui/input-group/InputGroupCharCount.vue
Normal file
81
src/components/ui/input-group/InputGroupCharCount.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<script setup>
|
||||
import { Hash, X } from 'lucide-vue-next';
|
||||
import { computed, useAttrs } from 'vue';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupButton,
|
||||
InputGroupInput,
|
||||
InputGroupText,
|
||||
InputGroupTextarea
|
||||
} from '.';
|
||||
|
||||
defineOptions({ inheritAttrs: false });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: [String, Number], default: '' },
|
||||
maxlength: { type: Number, required: true },
|
||||
multiline: { type: Boolean, default: false },
|
||||
class: { type: null, required: false },
|
||||
inputClass: { type: null, required: false },
|
||||
countClass: { type: null, required: false },
|
||||
iconClass: { type: null, required: false },
|
||||
clearable: { type: Boolean, default: false }
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const attrs = useAttrs();
|
||||
|
||||
const modelValue = useVModel(props, 'modelValue', emit, {
|
||||
passive: true,
|
||||
defaultValue: props.modelValue
|
||||
});
|
||||
|
||||
const valueLength = computed(() => String(modelValue.value ?? '').length);
|
||||
const remaining = computed(() => Math.max(props.maxlength - valueLength.value, 0));
|
||||
const wrapperClass = computed(() => cn(props.class, attrs.class));
|
||||
const inputClass = computed(() => cn(props.inputClass));
|
||||
const inputAttrs = computed(() => {
|
||||
const { class: _class, style: _style, ...rest } = attrs;
|
||||
return rest;
|
||||
});
|
||||
|
||||
const isDisabled = computed(() => Boolean(inputAttrs.value.disabled));
|
||||
|
||||
function clearValue() {
|
||||
if (isDisabled.value) return;
|
||||
modelValue.value = '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InputGroup :class="wrapperClass" :style="attrs.style">
|
||||
<InputGroupAddon v-if="$slots.leading" align="inline-start">
|
||||
<slot name="leading" />
|
||||
</InputGroupAddon>
|
||||
<component
|
||||
:is="props.multiline ? InputGroupTextarea : InputGroupInput"
|
||||
v-model="modelValue"
|
||||
:maxlength="props.maxlength"
|
||||
:class="inputClass"
|
||||
v-bind="inputAttrs" />
|
||||
<InputGroupAddon v-if="$slots.trailing" align="inline-end">
|
||||
<slot name="trailing" />
|
||||
</InputGroupAddon>
|
||||
<InputGroupAddon v-if="props.clearable && valueLength > 0" align="inline-end">
|
||||
<InputGroupButton size="icon-xs" :disabled="isDisabled" @click="clearValue">
|
||||
<X class="size-3.5" />
|
||||
<span class="sr-only">Clear</span>
|
||||
</InputGroupButton>
|
||||
</InputGroupAddon>
|
||||
<InputGroupAddon :align="props.multiline ? 'block-end' : 'inline-end'" v-if="valueLength > 0">
|
||||
<InputGroupText :class="cn('gap-1 tabular-nums text-xs', props.multiline && 'ml-auto', props.countClass)">
|
||||
<span>{{ valueLength }}</span>
|
||||
<span class="text-muted-foreground/70">/ {{ props.maxlength }}</span>
|
||||
</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</template>
|
||||
19
src/components/ui/input-group/InputGroupInput.vue
Normal file
19
src/components/ui/input-group/InputGroupInput.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false }
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Input
|
||||
data-slot="input-group-control"
|
||||
:class="
|
||||
cn(
|
||||
'flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent',
|
||||
props.class
|
||||
)
|
||||
" />
|
||||
</template>
|
||||
19
src/components/ui/input-group/InputGroupText.vue
Normal file
19
src/components/ui/input-group/InputGroupText.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false }
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
:class="
|
||||
cn(
|
||||
'text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4',
|
||||
props.class
|
||||
)
|
||||
">
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
19
src/components/ui/input-group/InputGroupTextarea.vue
Normal file
19
src/components/ui/input-group/InputGroupTextarea.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false }
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Textarea
|
||||
data-slot="input-group-control"
|
||||
:class="
|
||||
cn(
|
||||
'flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent',
|
||||
props.class
|
||||
)
|
||||
" />
|
||||
</template>
|
||||
48
src/components/ui/input-group/index.js
Normal file
48
src/components/ui/input-group/index.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
export { default as InputGroup } from './InputGroup.vue';
|
||||
export { default as InputGroupAddon } from './InputGroupAddon.vue';
|
||||
export { default as InputGroupButton } from './InputGroupButton.vue';
|
||||
export { default as InputGroupInput } from './InputGroupInput.vue';
|
||||
export { default as InputGroupText } from './InputGroupText.vue';
|
||||
export { default as InputGroupTextarea } from './InputGroupTextarea.vue';
|
||||
export { default as InputGroupCharCount } from './InputGroupCharCount.vue';
|
||||
|
||||
export const inputGroupAddonVariants = cva(
|
||||
"text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
align: {
|
||||
'inline-start':
|
||||
'order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]',
|
||||
'inline-end':
|
||||
'order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]',
|
||||
'block-start':
|
||||
'order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5',
|
||||
'block-end':
|
||||
'order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
align: 'inline-start'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const inputGroupButtonVariants = cva(
|
||||
'text-sm shadow-none flex gap-2 items-center',
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
|
||||
sm: 'h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5',
|
||||
'icon-xs':
|
||||
'size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0',
|
||||
'icon-sm': 'size-8 p-0 has-[>svg]:p-0'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'xs'
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1,32 +1,31 @@
|
||||
<script setup>
|
||||
import { useVModel } from "@vueuse/core";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
const props = defineProps({
|
||||
defaultValue: { type: [String, Number], required: false },
|
||||
modelValue: { type: [String, Number], required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
const props = defineProps({
|
||||
defaultValue: { type: [String, Number], required: false },
|
||||
modelValue: { type: [String, Number], required: false },
|
||||
class: { type: null, required: false }
|
||||
});
|
||||
|
||||
const emits = defineEmits(["update:modelValue"]);
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
|
||||
const modelValue = useVModel(props, "modelValue", emits, {
|
||||
passive: true,
|
||||
defaultValue: props.defaultValue,
|
||||
});
|
||||
const modelValue = useVModel(props, 'modelValue', emits, {
|
||||
passive: true,
|
||||
defaultValue: props.defaultValue
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input
|
||||
v-model="modelValue"
|
||||
data-slot="input"
|
||||
:class="
|
||||
cn(
|
||||
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
||||
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
/>
|
||||
<input
|
||||
v-model="modelValue"
|
||||
data-slot="input"
|
||||
:class="
|
||||
cn(
|
||||
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
||||
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||
props.class
|
||||
)
|
||||
" />
|
||||
</template>
|
||||
|
||||
29
src/components/ui/textarea/Textarea.vue
Normal file
29
src/components/ui/textarea/Textarea.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup>
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
defaultValue: { type: [String, Number], required: false },
|
||||
modelValue: { type: [String, Number], required: false }
|
||||
});
|
||||
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
|
||||
const modelValue = useVModel(props, 'modelValue', emits, {
|
||||
passive: true,
|
||||
defaultValue: props.defaultValue
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<textarea
|
||||
v-model="modelValue"
|
||||
data-slot="textarea"
|
||||
:class="
|
||||
cn(
|
||||
'border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
props.class
|
||||
)
|
||||
" />
|
||||
</template>
|
||||
1
src/components/ui/textarea/index.js
Normal file
1
src/components/ui/textarea/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Textarea } from './Textarea.vue';
|
||||
Reference in New Issue
Block a user