diff --git a/src/App.vue b/src/App.vue
index a0d7a19..ef16a8e 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,5 +1,3 @@
-
- Vue-Tailwind
-
+ Vue-Tailwind
diff --git a/src/components/DxhTextarea.vue b/src/components/DxhTextarea.vue
new file mode 100644
index 0000000..7da1dc3
--- /dev/null
+++ b/src/components/DxhTextarea.vue
@@ -0,0 +1,85 @@
+
+
+
+
+
diff --git a/src/components/__tests__/DxhTextarea.spec.ts b/src/components/__tests__/DxhTextarea.spec.ts
new file mode 100644
index 0000000..dae6fb6
--- /dev/null
+++ b/src/components/__tests__/DxhTextarea.spec.ts
@@ -0,0 +1,106 @@
+import { describe, it, expect } from 'vitest'
+import { mount } from '@vue/test-utils'
+import DTextarea from '../DxhTextarea.vue'
+
+describe('DTextarea', () => {
+ it('renders textarea element with label, placeholder, and hint text', () => {
+ const wrapper = mount(DTextarea, {
+ props: {
+ label: 'My Label',
+ modelValue: '',
+ placeholder: 'Enter your text',
+ hint: 'Hint text'
+ }
+ })
+
+ const label = wrapper.find('[data-test="textarea-label"]')
+ expect(label.exists()).toBe(true)
+
+ const textarea = wrapper.find('[data-test="textarea-element"]')
+ expect(textarea.exists()).toBe(true)
+ expect(textarea.attributes('placeholder')).toBe('Enter your text')
+
+ const hint = wrapper.find('[data-test="textarea-hint"]')
+ expect(hint.exists()).toBe(true)
+ expect(hint.text()).toBe('Hint text')
+ })
+
+ it('binds textarea value to modelValue prop', () => {
+ const wrapper = mount(DTextarea, {
+ props: {
+ modelValue: 'test'
+ }
+ })
+
+ const textareaElement = wrapper.find('[data-test="textarea-element"]')
+ .element as HTMLTextAreaElement
+ expect(textareaElement.value).toBe('test')
+ })
+
+ it('updates modelValue on textarea input', async () => {
+ const wrapper = mount(DTextarea, {
+ props: {
+ modelValue: ''
+ }
+ })
+
+ const textareaElement = wrapper.find('[data-test="textarea-element"]')
+ await textareaElement.setValue('new value')
+
+ const updateEvents = wrapper.emitted('update:modelValue')
+ expect(updateEvents).toBeTruthy()
+
+ const firstEventPayload = updateEvents ? updateEvents[0] : []
+ expect(firstEventPayload).toEqual(['new value'])
+ })
+
+ it('emits focus event when textarea is focused', async () => {
+ const wrapper = mount(DTextarea, {
+ props: {
+ modelValue: ''
+ }
+ })
+
+ await wrapper.find('[data-test="textarea-element"]').trigger('focus')
+ expect(wrapper.emitted('focus')).toBeTruthy()
+ })
+
+ it('emits blur event when textarea is blurred', async () => {
+ const wrapper = mount(DTextarea, {
+ props: {
+ modelValue: ''
+ }
+ })
+
+ await wrapper.find('[data-test="textarea-element"]').trigger('blur')
+ expect(wrapper.emitted('blur')).toBeTruthy()
+ })
+
+ it('emits enter event when Enter key is pressed', async () => {
+ const wrapper = mount(DTextarea, {
+ props: {
+ modelValue: ''
+ }
+ })
+
+ await wrapper.find('[data-test="textarea-element"]').trigger('keyup.enter')
+ expect(wrapper.emitted('enter')).toBeTruthy()
+ })
+
+ it('clears textarea value when clearable and close button is clicked', async () => {
+ const wrapper = mount(DTextarea, {
+ props: {
+ modelValue: 'test',
+ clearable: true
+ }
+ })
+
+ const closeButton = wrapper.find('[data-test="textarea-clear-button"]')
+ expect(closeButton.exists()).toBe(true)
+
+ await closeButton.trigger('click')
+ const textareaElement = wrapper.find('[data-test="textarea-element"]')
+ .element as HTMLTextAreaElement
+ expect(textareaElement.value).not.toBe('')
+ })
+})
diff --git a/src/index.ts b/src/index.ts
index ee2ff8d..ff6b1d2 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,4 +1,5 @@
-import DButton from "./components/DButton.vue"
-import DInput from "./components/DInput.vue"
+import DButton from './components/DButton.vue'
+import DInput from './components/DInput.vue'
+import DxhTextarea from './components/DxhTextarea.vue'
-export default {DButton, DInput}
\ No newline at end of file
+export default { DButton, DInput, DxhTextarea }