-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
242 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
<!-- | ||
Copyright (C) 2024 Nethesis S.r.l. | ||
SPDX-License-Identifier: GPL-3.0-or-later | ||
--> | ||
|
||
<script setup lang="ts"> | ||
import { computed } from 'vue' | ||
import { v4 as uuidv4 } from 'uuid' | ||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' | ||
import { library } from '@fortawesome/fontawesome-svg-core' | ||
import { faCircleExclamation as fasCircleExclamation } from '@fortawesome/free-solid-svg-icons' | ||
|
||
const props = defineProps({ | ||
label: { | ||
type: String, | ||
default: '' | ||
}, | ||
rows: { | ||
type: Number, | ||
default: 4 | ||
}, | ||
modelValue: { | ||
type: String, | ||
default: '' | ||
}, | ||
id: { | ||
type: String, | ||
default: '' | ||
}, | ||
placeholder: { | ||
type: String, | ||
default: '' | ||
}, | ||
helperText: { | ||
type: String, | ||
default: '' | ||
}, | ||
invalidMessage: { | ||
type: String, | ||
default: '' | ||
}, | ||
optional: { | ||
type: Boolean | ||
}, | ||
optionalLabel: { | ||
type: String, | ||
default: 'Optional' | ||
}, | ||
disabled: { | ||
type: Boolean, | ||
default: false | ||
} | ||
}) | ||
|
||
const emit = defineEmits(['update:modelValue']) | ||
|
||
// add fontawesome icons | ||
library.add(fasCircleExclamation) | ||
|
||
const textAreaBaseStyle = | ||
'block w-full rounded-md border-0 py-1.5 ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6 disabled:cursor-not-allowed disabled:opacity-50 text-gray-900 bg-white placeholder:text-gray-400 transition-colors duration-200 dark:text-gray-50 dark:bg-gray-950 dark:placeholder:text-gray-500' | ||
const textAreaValidStyle = | ||
'ring-gray-300 focus:ring-primary-500 dark:ring-gray-600 dark:focus:ring-primary-300' | ||
const textAreaInvalidStyle = | ||
'pr-10 ring-rose-300 focus:ring-rose-500 ring-rose-700 focus:ring-rose-500' | ||
|
||
const descriptionBaseStyle = 'mt-2 text-sm' | ||
|
||
const componentId = computed(() => (props.id ? props.id : uuidv4())) | ||
|
||
const textAreaStyles = computed(() => | ||
[textAreaBaseStyle, props.invalidMessage ? textAreaInvalidStyle : textAreaValidStyle].join(' ') | ||
) | ||
|
||
function emitModelValue(ev: any) { | ||
emit('update:modelValue', ev.target.value) | ||
} | ||
</script> | ||
|
||
<template> | ||
<div> | ||
<label | ||
v-if="label" | ||
:for="componentId" | ||
class="mb-2 flex items-end justify-between text-sm font-medium leading-6 text-gray-700 dark:text-gray-200" | ||
> | ||
<span> | ||
{{ label }} | ||
<span v-if="$slots.tooltip" class="ml-1"> | ||
<slot name="tooltip"></slot> | ||
</span> | ||
</span> | ||
<span v-if="optional">{{ optionalLabel }}</span> | ||
</label> | ||
<div class="relative rounded-md shadow-sm"> | ||
<textarea | ||
:id="componentId" | ||
:value="modelValue" | ||
:rows="rows" | ||
:placeholder="placeholder" | ||
:aria-describedby="componentId + '-description'" | ||
v-bind="$attrs" | ||
:disabled="disabled" | ||
:class="textAreaStyles" | ||
@input="($event) => emitModelValue($event)" | ||
/> | ||
<!-- invalid icon --> | ||
<div v-if="invalidMessage" class="pointer-events-none absolute inset-y-0 right-0 pr-3 pt-2"> | ||
<font-awesome-icon | ||
:icon="['fas', 'circle-exclamation']" | ||
class="h-4 w-4 text-rose-700 dark:text-rose-400" | ||
aria-hidden="true" | ||
/> | ||
</div> | ||
</div> | ||
<!-- invalid message --> | ||
<p | ||
v-if="invalidMessage" | ||
:id="componentId + '-description'" | ||
:class="[descriptionBaseStyle, 'text-rose-700 dark:text-rose-400']" | ||
> | ||
{{ invalidMessage }} | ||
</p> | ||
<!-- helper text --> | ||
<p | ||
v-else-if="helperText" | ||
:id="componentId + '-description'" | ||
:class="[descriptionBaseStyle, 'text-gray-500 dark:text-gray-400']" | ||
> | ||
{{ helperText }} | ||
</p> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// Copyright (C) 2024 Nethesis S.r.l. | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
import type { Meta, StoryObj } from '@storybook/vue3' | ||
|
||
import { NeTextArea, NeTooltip } from '../src/main' | ||
|
||
const meta = { | ||
title: 'Control/NeTextArea', | ||
component: NeTextArea, | ||
// default values | ||
args: { | ||
label: 'Label', | ||
rows: 4, | ||
modelValue: '', | ||
placeholder: 'Placeholder', | ||
helperText: '', | ||
invalidMessage: '', | ||
optional: false, | ||
disabled: false, | ||
id: '', | ||
optionalLabel: 'Optional' | ||
} | ||
} satisfies Meta<typeof NeTextArea> | ||
|
||
export default meta | ||
type Story = StoryObj<typeof meta> | ||
|
||
const template = '<NeTextArea v-bind="args" class="max-w-md" />' | ||
|
||
export const Default: Story = { | ||
render: (args) => ({ | ||
components: { NeTextArea }, | ||
setup() { | ||
return { args } | ||
}, | ||
template: template | ||
}), | ||
args: {} | ||
} | ||
|
||
export const HelperText: Story = { | ||
render: (args) => ({ | ||
components: { NeTextArea }, | ||
setup() { | ||
return { args } | ||
}, | ||
template: template | ||
}), | ||
args: { helperText: 'Helper text' } | ||
} | ||
|
||
export const Invalid: Story = { | ||
render: (args) => ({ | ||
components: { NeTextArea }, | ||
setup() { | ||
return { args } | ||
}, | ||
template: template | ||
}), | ||
args: { | ||
invalidMessage: 'Invalid input value' | ||
} | ||
} | ||
|
||
export const Optional: Story = { | ||
render: (args) => ({ | ||
components: { NeTextArea }, | ||
setup() { | ||
return { args } | ||
}, | ||
template: template | ||
}), | ||
args: { | ||
optional: true | ||
} | ||
} | ||
|
||
export const Disabled: Story = { | ||
render: (args) => ({ | ||
components: { NeTextArea }, | ||
setup() { | ||
return { args } | ||
}, | ||
template: template | ||
}), | ||
args: { disabled: true } | ||
} | ||
|
||
const templateWithTooltip = | ||
'<NeTextArea v-bind="args" class="max-w-md">\ | ||
<template #tooltip>\ | ||
<NeTooltip>\ | ||
<template #content>Tooltip</template>\ | ||
</NeTooltip>\ | ||
</template>\ | ||
</NeTextArea>' | ||
|
||
export const WithTooltip: Story = { | ||
render: (args) => ({ | ||
components: { NeTextArea, NeTooltip }, | ||
setup() { | ||
return { args } | ||
}, | ||
template: templateWithTooltip | ||
}), | ||
args: {} | ||
} |