Skip to content

Commit

Permalink
Merge pull request #76 from tournantdev/input/textarea-help-placement
Browse files Browse the repository at this point in the history
  • Loading branch information
ovlb committed Oct 13, 2020
2 parents f31c90c + de67b49 commit fa24380
Show file tree
Hide file tree
Showing 6 changed files with 2,687 additions and 1,878 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
},
"dependencies": {
"@babel/core": "^7.8.3",
"@storybook/addon-knobs": "^5.3.4",
"@storybook/vue": "^5.3.4",
"@storybook/addon-knobs": "5.3.21",
"@storybook/vue": "5.3.21",
"@tournant/communard": "^2.2.0",
"@vue/babel-preset-app": "^4.1.2",
"@vue/cli-plugin-babel": "^4.1.2",
Expand All @@ -32,6 +32,7 @@
"@vue/test-utils": "1.0.0-beta.29",
"babel-eslint": "^10.0.1",
"babel-jest": "^24.9.0",
"babel-loader": "^8.1.0",
"babel-preset-vue": "^2.0.2",
"codecov": "^3.5.0",
"eslint": "^6.7.1",
Expand Down
35 changes: 29 additions & 6 deletions packages/input/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ This is just a quick overview. For an in-depth guide how to use the component ch
- `label`: The label text of the input. Required.
- `validation`: A vuelidate-like object, must contain properties named `$error` and `$dirty`. Required.
- `description`: Descriptive text giving a user more context on what form their input has to have. Optional.
- `descriptionPosition`: Controls if the position should be displayed underneath the input or between label and input; defaults to `bottom`.
- `isTextarea`: Render a textarea instead of an input element. Default to `false`.

### Styles

Expand Down Expand Up @@ -92,6 +94,32 @@ Ths will result in the following input:

💁‍ _Note:_ You do not need to pass in a `id`. A unique ID for every instance of the component is automatically generated.

### Textarea

`<input>`s and `<textarea>`s are quite similar, in that they can both hold text content, which might need validation and so forth. This is why, instead of having a separate component to add a `<textarea>`, you can change this one via the `isTextarea` prop to be one:

```html
<tournant-input
v-model="message"
:is-textarea="true"
name="message"
label="Your message"
/>
```

will output

```html
<label class="t-ui-input__label" for="6ac26f8f-930c-4dc4-a098-b00094b56906">
Your message
</label>
<textarea
class="t-ui-input__input"
name="message"
id="6ac26f8f-930c-4dc4-a098-b00094b56906"
></textarea>
```

### Label

Input elements [must have a linked label](https://www.w3.org/TR/WCAG20-TECHS/H44.html) to give the input an accessible name.
Expand Down Expand Up @@ -157,7 +185,7 @@ No input without validation, right?

You will have to take care of this yourself, though. The component can and should not know what value is expected inside of it.

Nonetheless, I tried to make it as easy as possible to use the component along existing solutions like [Vuelidate](https://vuelidate.netlify.com/).
Nonetheless, we tried to make it as easy as possible to use the component along existing solutions like [Vuelidate](https://vuelidate.netlify.com/).

In fact, if you are already using Vuelidate, you are good to go.

Expand Down Expand Up @@ -205,11 +233,6 @@ This attribute could also be used to add styles based on the validated state.
.tournant-input__input[aria-invalid='true'] {
border-color: red;
}

/** [data-untouched is set on the input while `validation.$dirty is `false``] and can be used to only apply validated styles to touched and validated inputs */
.tournant-input__input[aria-invalid='false']:not([data-untouched]) {
border-color: green;
}
```

### Feedback Messages
Expand Down
34 changes: 30 additions & 4 deletions packages/input/src/index.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
<template>
<div class="t-ui-input">
<label :for="id" class="t-ui-input__label">
<label :for="id" class="t-ui-input__label" data-test="label">
{{ label }}
<slot name="label-text" />
</label>
<input
<p
v-if="description && descriptionPosition === 'top'"
:id="`${id}__desc`"
class="t-ui-input__description"
data-test="description-top"
>
{{ description }}
</p>
<component
:is="isTextarea ? 'textarea' : 'input'"
:id="id"
:value="value"
:aria-invalid="validation.$error.toString()"
:aria-describedby="ariaDescribedby"
v-bind="$attrs"
class="t-ui-input__input"
data-test="input"
v-on="listeners"
@input="updateValue"
/>
<p v-if="description" :id="`${id}__desc`" class="t-ui-input__description">
>
{{ value }}
</component>
<p
v-if="description && descriptionPosition === 'bottom'"
:id="`${id}__desc`"
class="t-ui-input__description"
data-test="description-bottom"
>
{{ description }}
</p>
<div
Expand Down Expand Up @@ -42,13 +59,22 @@ export default {
type: String,
default: ''
},
descriptionPosition: {
type: String,
default: 'bottom',
validator: position => position === 'top' || position === 'bottom'
},
label: {
type: String,
required: true
},
validation: {
type: Object,
required: true
},
isTextarea: {
type: Boolean,
default: false
}
},
data() {
Expand Down
54 changes: 51 additions & 3 deletions packages/input/tests/input.stories.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import TournantInput from '../src/index.vue'
import { withKnobs, text } from '@storybook/addon-knobs'
import { withKnobs, text, radios } from '@storybook/addon-knobs'

// const dataNoError = { $error: false, $dirty: false }

Expand All @@ -15,7 +15,7 @@ export const withLabel = () => {
},
data: () => ({
validation: { $error: false, $dirty: false },
name: ''
name: 'Tournant'
}),
template: `<tournant-input :label="label" :validation="validation" value="" v-model="name" type="text" />`
}
Expand All @@ -30,6 +30,30 @@ export const typePassword = () => ({
template: `<tournant-input label="password" :validation="validation" value="" v-model="password" type="password" />`
})

export const asTextarea = () => ({
components: { TournantInput },
data: () => ({
validation: { $error: false, $dirty: false },
message: 'Hello'
}),
template: `<tournant-input label="Your message" :value="message" :validation="validation" v-model="message" :is-textarea="true" />`
})

export const asTextareaWithError = () => ({
components: { TournantInput },
data: () => ({
validation: { $error: true, $dirty: false },
message: 'Hello'
}),
template: `
<tournant-input label="Your message" :value="message" :validation="validation" v-model="message" :is-textarea="true">
<template v-slot:feedback>
<p>Please enter your message</p>
</template>
</tournant-input>
`
})

export const withError = () => ({
components: { TournantInput },
data: () => ({
Expand All @@ -43,11 +67,35 @@ export const withError = () => ({
</tournant-input>`
})

const descriptionPositions = {
Top: 'top',
Bottom: 'bottom'
}

export const withDescription = () => ({
components: { TournantInput },
props: {
descriptionPosition: {
default: radios(
'Description position',
descriptionPositions,
'bottom',
'tuidescpos'
)
}
},
data: () => ({
validation: { $error: false, $dirty: false },
password: ''
}),
template: `<tournant-input label="password" :validation="validation" value="" v-model="password" type="password" description="Your password must be unique to this site." />`
template: `
<tournant-input
label="password"
:validation="validation"
value=""
v-model="password"
type="password"
description="Your password must be unique to this site."
:description-position="descriptionPosition" />
`
})
37 changes: 37 additions & 0 deletions packages/input/tests/unit/Input.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,20 @@ describe('Input', () => {
expect(type).toBe('password')
})

it('renders an input element by default', () => {
const input = wrapper.find('[data-test="input"]')

expect(input.element.tagName).toBe('INPUT')
})

it('renders a textarea if instructed', () => {
wrapper.setProps({ isTextarea: true })

const input = wrapper.find('[data-test="input"]')

expect(input.element.tagName).toBe('TEXTAREA')
})

describe('`v-model` compatibility', () => {
it('sets the value prop on the input', () => {
const $input = wrapper.find('input')
Expand Down Expand Up @@ -136,6 +150,29 @@ describe('Input', () => {
`${feedbackId} ${descId}`
)
})

it('positions the description text underneath the input by default', () => {
wrapper.setProps({
description: 'This is a description',
validation: { $error: true }
})

const description = wrapper.find('[data-test="description-bottom"]')

expect(description.exists()).toBe(true)
})

it('allows to position the description text above the input', () => {
wrapper.setProps({
description: 'This is a description',
descriptionPosition: 'top',
validation: { $error: true }
})

const description = wrapper.find('[data-test="description-top"]')

expect(description.exists()).toBe(true)
})
})

describe('slots', () => {
Expand Down
Loading

0 comments on commit fa24380

Please sign in to comment.