Skip to content

Commit fa24380

Browse files
authored
Merge pull request #76 from tournantdev/input/textarea-help-placement
2 parents f31c90c + de67b49 commit fa24380

File tree

6 files changed

+2687
-1878
lines changed

6 files changed

+2687
-1878
lines changed

Diff for: package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
},
2121
"dependencies": {
2222
"@babel/core": "^7.8.3",
23-
"@storybook/addon-knobs": "^5.3.4",
24-
"@storybook/vue": "^5.3.4",
23+
"@storybook/addon-knobs": "5.3.21",
24+
"@storybook/vue": "5.3.21",
2525
"@tournant/communard": "^2.2.0",
2626
"@vue/babel-preset-app": "^4.1.2",
2727
"@vue/cli-plugin-babel": "^4.1.2",
@@ -32,6 +32,7 @@
3232
"@vue/test-utils": "1.0.0-beta.29",
3333
"babel-eslint": "^10.0.1",
3434
"babel-jest": "^24.9.0",
35+
"babel-loader": "^8.1.0",
3536
"babel-preset-vue": "^2.0.2",
3637
"codecov": "^3.5.0",
3738
"eslint": "^6.7.1",

Diff for: packages/input/README.md

+29-6
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ This is just a quick overview. For an in-depth guide how to use the component ch
4646
- `label`: The label text of the input. Required.
4747
- `validation`: A vuelidate-like object, must contain properties named `$error` and `$dirty`. Required.
4848
- `description`: Descriptive text giving a user more context on what form their input has to have. Optional.
49+
- `descriptionPosition`: Controls if the position should be displayed underneath the input or between label and input; defaults to `bottom`.
50+
- `isTextarea`: Render a textarea instead of an input element. Default to `false`.
4951

5052
### Styles
5153

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

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

97+
### Textarea
98+
99+
`<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:
100+
101+
```html
102+
<tournant-input
103+
v-model="message"
104+
:is-textarea="true"
105+
name="message"
106+
label="Your message"
107+
/>
108+
```
109+
110+
will output
111+
112+
```html
113+
<label class="t-ui-input__label" for="6ac26f8f-930c-4dc4-a098-b00094b56906">
114+
Your message
115+
</label>
116+
<textarea
117+
class="t-ui-input__input"
118+
name="message"
119+
id="6ac26f8f-930c-4dc4-a098-b00094b56906"
120+
></textarea>
121+
```
122+
95123
### Label
96124

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

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

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

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

@@ -205,11 +233,6 @@ This attribute could also be used to add styles based on the validated state.
205233
.tournant-input__input[aria-invalid='true'] {
206234
border-color: red;
207235
}
208-
209-
/** [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 */
210-
.tournant-input__input[aria-invalid='false']:not([data-untouched]) {
211-
border-color: green;
212-
}
213236
```
214237

215238
### Feedback Messages

Diff for: packages/input/src/index.vue

+30-4
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,37 @@
11
<template>
22
<div class="t-ui-input">
3-
<label :for="id" class="t-ui-input__label">
3+
<label :for="id" class="t-ui-input__label" data-test="label">
44
{{ label }}
55
<slot name="label-text" />
66
</label>
7-
<input
7+
<p
8+
v-if="description && descriptionPosition === 'top'"
9+
:id="`${id}__desc`"
10+
class="t-ui-input__description"
11+
data-test="description-top"
12+
>
13+
{{ description }}
14+
</p>
15+
<component
16+
:is="isTextarea ? 'textarea' : 'input'"
817
:id="id"
918
:value="value"
1019
:aria-invalid="validation.$error.toString()"
1120
:aria-describedby="ariaDescribedby"
1221
v-bind="$attrs"
1322
class="t-ui-input__input"
23+
data-test="input"
1424
v-on="listeners"
1525
@input="updateValue"
16-
/>
17-
<p v-if="description" :id="`${id}__desc`" class="t-ui-input__description">
26+
>
27+
{{ value }}
28+
</component>
29+
<p
30+
v-if="description && descriptionPosition === 'bottom'"
31+
:id="`${id}__desc`"
32+
class="t-ui-input__description"
33+
data-test="description-bottom"
34+
>
1835
{{ description }}
1936
</p>
2037
<div
@@ -42,13 +59,22 @@ export default {
4259
type: String,
4360
default: ''
4461
},
62+
descriptionPosition: {
63+
type: String,
64+
default: 'bottom',
65+
validator: position => position === 'top' || position === 'bottom'
66+
},
4567
label: {
4668
type: String,
4769
required: true
4870
},
4971
validation: {
5072
type: Object,
5173
required: true
74+
},
75+
isTextarea: {
76+
type: Boolean,
77+
default: false
5278
}
5379
},
5480
data() {

Diff for: packages/input/tests/input.stories.js

+51-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import TournantInput from '../src/index.vue'
2-
import { withKnobs, text } from '@storybook/addon-knobs'
2+
import { withKnobs, text, radios } from '@storybook/addon-knobs'
33

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

@@ -15,7 +15,7 @@ export const withLabel = () => {
1515
},
1616
data: () => ({
1717
validation: { $error: false, $dirty: false },
18-
name: ''
18+
name: 'Tournant'
1919
}),
2020
template: `<tournant-input :label="label" :validation="validation" value="" v-model="name" type="text" />`
2121
}
@@ -30,6 +30,30 @@ export const typePassword = () => ({
3030
template: `<tournant-input label="password" :validation="validation" value="" v-model="password" type="password" />`
3131
})
3232

33+
export const asTextarea = () => ({
34+
components: { TournantInput },
35+
data: () => ({
36+
validation: { $error: false, $dirty: false },
37+
message: 'Hello'
38+
}),
39+
template: `<tournant-input label="Your message" :value="message" :validation="validation" v-model="message" :is-textarea="true" />`
40+
})
41+
42+
export const asTextareaWithError = () => ({
43+
components: { TournantInput },
44+
data: () => ({
45+
validation: { $error: true, $dirty: false },
46+
message: 'Hello'
47+
}),
48+
template: `
49+
<tournant-input label="Your message" :value="message" :validation="validation" v-model="message" :is-textarea="true">
50+
<template v-slot:feedback>
51+
<p>Please enter your message</p>
52+
</template>
53+
</tournant-input>
54+
`
55+
})
56+
3357
export const withError = () => ({
3458
components: { TournantInput },
3559
data: () => ({
@@ -43,11 +67,35 @@ export const withError = () => ({
4367
</tournant-input>`
4468
})
4569

70+
const descriptionPositions = {
71+
Top: 'top',
72+
Bottom: 'bottom'
73+
}
74+
4675
export const withDescription = () => ({
4776
components: { TournantInput },
77+
props: {
78+
descriptionPosition: {
79+
default: radios(
80+
'Description position',
81+
descriptionPositions,
82+
'bottom',
83+
'tuidescpos'
84+
)
85+
}
86+
},
4887
data: () => ({
4988
validation: { $error: false, $dirty: false },
5089
password: ''
5190
}),
52-
template: `<tournant-input label="password" :validation="validation" value="" v-model="password" type="password" description="Your password must be unique to this site." />`
91+
template: `
92+
<tournant-input
93+
label="password"
94+
:validation="validation"
95+
value=""
96+
v-model="password"
97+
type="password"
98+
description="Your password must be unique to this site."
99+
:description-position="descriptionPosition" />
100+
`
53101
})

Diff for: packages/input/tests/unit/Input.spec.js

+37
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,20 @@ describe('Input', () => {
6060
expect(type).toBe('password')
6161
})
6262

63+
it('renders an input element by default', () => {
64+
const input = wrapper.find('[data-test="input"]')
65+
66+
expect(input.element.tagName).toBe('INPUT')
67+
})
68+
69+
it('renders a textarea if instructed', () => {
70+
wrapper.setProps({ isTextarea: true })
71+
72+
const input = wrapper.find('[data-test="input"]')
73+
74+
expect(input.element.tagName).toBe('TEXTAREA')
75+
})
76+
6377
describe('`v-model` compatibility', () => {
6478
it('sets the value prop on the input', () => {
6579
const $input = wrapper.find('input')
@@ -136,6 +150,29 @@ describe('Input', () => {
136150
`${feedbackId} ${descId}`
137151
)
138152
})
153+
154+
it('positions the description text underneath the input by default', () => {
155+
wrapper.setProps({
156+
description: 'This is a description',
157+
validation: { $error: true }
158+
})
159+
160+
const description = wrapper.find('[data-test="description-bottom"]')
161+
162+
expect(description.exists()).toBe(true)
163+
})
164+
165+
it('allows to position the description text above the input', () => {
166+
wrapper.setProps({
167+
description: 'This is a description',
168+
descriptionPosition: 'top',
169+
validation: { $error: true }
170+
})
171+
172+
const description = wrapper.find('[data-test="description-top"]')
173+
174+
expect(description.exists()).toBe(true)
175+
})
139176
})
140177

141178
describe('slots', () => {

0 commit comments

Comments
 (0)