-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Upstream TextArea component from Mismatch Finder * Add TextArea story (#460) Co-authored-by: Silvan Heintze <[email protected]> Bug: T289138 Bug: T289141
- Loading branch information
1 parent
74d7753
commit bf4e40e
Showing
4 changed files
with
372 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,14 @@ | ||
enum ResizeLimit { | ||
Horizontal = 'horizontal', | ||
Vertical = 'vertical', | ||
None = 'none' | ||
} | ||
|
||
function validateLimit( limit: string ): boolean { | ||
return Object.values( ResizeLimit ).includes( limit as ResizeLimit ); | ||
} | ||
|
||
export { | ||
ResizeLimit, | ||
validateLimit, | ||
}; |
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,201 @@ | ||
<template> | ||
<div class="wikit wikit-TextArea"> | ||
<span class="wikit-TextArea__label-wrapper"> | ||
<label | ||
:class="[ | ||
'wikit-TextArea__label' | ||
]" | ||
:for="id" | ||
> | ||
{{ label }} | ||
</label> | ||
</span> | ||
<textarea | ||
:id="id" | ||
:class="[ | ||
'wikit-TextArea__textarea', | ||
`wikit-TextArea__textarea--${resizeType}` | ||
]" | ||
:value="value" | ||
:rows="rows" | ||
:placeholder="placeholder" | ||
label="" | ||
@input="$emit( 'input', $event.target.value )" | ||
/> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import Vue from 'vue'; | ||
import generateId from '@/components/util/generateUid'; | ||
import { ResizeLimit, validateLimit } from '@/components/ResizeLimit'; | ||
/** | ||
* Text areas are multi-line, non auto-sizing input fields that allow manual resizing by users. | ||
*/ | ||
export default Vue.extend( { | ||
props: { | ||
/** | ||
* An initial value for the textarea | ||
*/ | ||
value: { | ||
type: String, | ||
default: '', | ||
}, | ||
/** | ||
* The text area label | ||
*/ | ||
label: { | ||
type: String, | ||
default: '', | ||
}, | ||
/** | ||
* The text area placeholder | ||
*/ | ||
placeholder: { | ||
type: String, | ||
default: '', | ||
}, | ||
/** | ||
* Defines the amount of lines of text that the text area can take by | ||
* default before scroll is triggered, therefore influencing the height | ||
* of the component. | ||
*/ | ||
rows: { | ||
type: Number, | ||
default: 2, | ||
}, | ||
/** | ||
* Allows users to expand the component horizontally or vertically | ||
* using the expand handler. It can be used to entirely disable manual | ||
* resizing. | ||
* | ||
* Allowed values: `vertical`, `horizontal`, `none` | ||
*/ | ||
resize: { | ||
type: String, | ||
validator( value: ResizeLimit ): boolean { | ||
return validateLimit( value ); | ||
}, | ||
default: ResizeLimit.Vertical, | ||
}, | ||
}, | ||
data() { | ||
return { | ||
// https://github.com/vuejs/vue/issues/5886 | ||
id: generateId( 'wikit-TextArea' ), | ||
}; | ||
}, | ||
computed: { | ||
resizeType(): string { | ||
// Unfortunately, the vue prop validator does not throw or falls | ||
// back to default values on validation failure, therefore, we need | ||
// to check for a valid resize limit value | ||
return validateLimit( this.resize ) ? this.resize : 'vertical'; | ||
}, | ||
}, | ||
} ); | ||
</script> | ||
|
||
<style lang="scss"> | ||
.wikit-TextArea { | ||
&__label-wrapper { | ||
display: flex; | ||
align-items: center; | ||
gap: $dimension-spacing-small; | ||
} | ||
&__label { | ||
@include Label('block'); | ||
} | ||
} | ||
.wikit-TextArea__textarea { | ||
display: block; | ||
width: 100%; | ||
// The default resizing behaviour should be on the y axis only | ||
resize: vertical; | ||
/** | ||
* Colors | ||
*/ | ||
color: $wikit-Input-color; | ||
background-color: $wikit-Input-background-color; | ||
/** | ||
* Typography | ||
*/ | ||
font-family: $wikit-Input-font-family; | ||
font-size: $wikit-Input-font-size; | ||
font-weight: $wikit-Input-font-weight; | ||
line-height: $wikit-Input-line-height; | ||
/** | ||
* Spacing | ||
*/ | ||
padding-inline: $wikit-Input-desktop-padding-inline; | ||
padding-block: $wikit-Input-desktop-padding-block; | ||
@media (max-width: $width-breakpoint-mobile) { | ||
padding-inline: $wikit-Input-mobile-padding-inline; | ||
padding-block: $wikit-Input-mobile-padding-block; | ||
} | ||
/** | ||
* Borders | ||
*/ | ||
border-color: $wikit-Input-border-color; | ||
border-style: $wikit-Input-border-style; | ||
border-width: $wikit-Input-border-width; | ||
border-radius: $wikit-Input-border-radius; | ||
/** | ||
* Animation | ||
*/ | ||
// Sets a basis for the inset box-shadow transition which otherwise doesn't work in Firefox. | ||
// https://stackoverflow.com/questions/25410207/css-transition-not-working-on-box-shadow-property/25410897 | ||
// TODO: replace by token | ||
box-shadow: inset 0 0 0 1px transparent; | ||
transition-duration: $wikit-Input-transition-duration; | ||
transition-timing-function: $wikit-Input-transition-timing-function; | ||
transition-property: $wikit-Input-transition-property; | ||
/** | ||
* State overrides | ||
*/ | ||
&:hover { | ||
border-color: $wikit-Input-hover-border-color; | ||
} | ||
&:focus, | ||
&:active { | ||
border-color: $wikit-Input-active-border-color; | ||
box-shadow: $wikit-Input-active-box-shadow; | ||
} | ||
&:focus { | ||
outline: none; | ||
} | ||
&::placeholder { | ||
font-family: $wikit-Input-placeholder-font-family; | ||
font-size: $wikit-Input-placeholder-font-size; | ||
font-weight: $wikit-Input-placeholder-font-weight; | ||
line-height: $wikit-Input-placeholder-line-height; | ||
color: $wikit-Input-placeholder-color; | ||
} | ||
/** | ||
* Property overrides | ||
*/ | ||
&--horizontal { | ||
resize: horizontal; | ||
} | ||
&--none { | ||
resize: none; | ||
} | ||
} | ||
</style> |
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,84 @@ | ||
import TextArea from '@/components/TextArea'; | ||
import { Component } from 'vue'; | ||
|
||
export default { | ||
component: TextArea, | ||
title: 'TextArea', | ||
}; | ||
|
||
export function basic( args: object ): Component { | ||
return { | ||
data(): object { | ||
return { args }; | ||
}, | ||
components: { TextArea }, | ||
props: Object.keys( args ), | ||
template: ` | ||
<div style="max-width: 75%"> | ||
<TextArea | ||
:label="label" | ||
:placeholder="placeholder" | ||
:rows="rows" | ||
:resize="resize" | ||
/> | ||
</div> | ||
`, | ||
}; | ||
} | ||
|
||
basic.args = { | ||
label: 'Label', | ||
placeholder: 'Placeholder' | ||
}; | ||
|
||
basic.argTypes = { | ||
value: { | ||
control: false | ||
}, | ||
label: { | ||
control: { | ||
type: 'text', | ||
}, | ||
}, | ||
placeholder: { | ||
control: { | ||
type: 'text', | ||
}, | ||
}, | ||
rows: { | ||
control: { | ||
type: 'number', | ||
}, | ||
}, | ||
resize: { | ||
table: { | ||
defaultValue: { | ||
summary: 'vertical' | ||
} | ||
}, | ||
control: { | ||
type: 'select', | ||
options: ['vertical', 'horizontal', 'none'], | ||
default: 'vertical' | ||
}, | ||
}, | ||
input : { | ||
description: 'Emitted on each character input to the textarea, contains the entire string value of the textarea itself.', | ||
table: { | ||
type: { | ||
summary: 'string' | ||
} | ||
} | ||
} | ||
}; | ||
|
||
export function all(): Component { | ||
return { | ||
components: { TextArea }, | ||
template: ` | ||
<div style="max-width: 95%"> | ||
<TextArea label="Label" placeholder="Placeholder" /> | ||
</div> | ||
`, | ||
}; | ||
} |
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,73 @@ | ||
import { mount } from '@vue/test-utils'; | ||
import TextArea from '@/components/TextArea.vue'; | ||
import { ResizeLimit } from '@/components/ResizeLimit.ts'; | ||
|
||
describe( 'TextArea.vue', () => { | ||
it( 'accepts rows property', () => { | ||
const wrapper = mount( TextArea, { | ||
propsData: { rows: 42 }, | ||
} ); | ||
|
||
expect( wrapper.props().rows ).toBe( 42 ); | ||
expect( wrapper.find( 'textarea' ).attributes( 'rows' ) ).toBe( '42' ); | ||
} ); | ||
|
||
it( 'accepts resize property', () => { | ||
const wrapper = mount( TextArea, { | ||
propsData: { resize: ResizeLimit.Horizontal }, | ||
} ); | ||
|
||
expect( wrapper.props().resize ).toBe( ResizeLimit.Horizontal ); | ||
expect( wrapper.find( 'textarea' ).classes() ).toContain( 'wikit-TextArea__textarea--horizontal' ); | ||
} ); | ||
|
||
it( 'uses default resize value', () => { | ||
const wrapper = mount( TextArea ); | ||
|
||
expect( wrapper.find( 'textarea' ).classes() ).toContain( 'wikit-TextArea__textarea--vertical' ); | ||
} ); | ||
|
||
it( 'throws on invalid resize values', () => { | ||
expect( () => mount( TextArea, { | ||
propsData: { resize: 'nonsense' }, | ||
} ) ).toThrow( 'Invalid prop: custom validator check failed for prop "resize"' ); | ||
} ); | ||
|
||
it( 'accepts a textarea value', () => { | ||
const value = 'Some beautiful value!'; | ||
const wrapper = mount( TextArea, { | ||
propsData: { value }, | ||
} ); | ||
const element = wrapper.find( 'textarea' ).element as HTMLFormElement; | ||
|
||
expect( wrapper.props().value ).toBe( value ); | ||
expect( element.value ).toBe( value ); | ||
} ); | ||
|
||
it( 'accepts label property', () => { | ||
const label = 'da Label'; | ||
const wrapper = mount( TextArea, { | ||
propsData: { label }, | ||
} ); | ||
|
||
expect( wrapper.props().label ).toBe( label ); | ||
expect( wrapper.find( 'label' ).text() ).toBe( label ); | ||
} ); | ||
|
||
it( 'accepts placeholder property', () => { | ||
const placeholder = 'This is a placeholder'; | ||
const wrapper = mount( TextArea, { | ||
propsData: { placeholder }, | ||
} ); | ||
|
||
expect( wrapper.find( 'textarea' ).attributes( 'placeholder' ) ).toBe( placeholder ); | ||
} ); | ||
|
||
it( 'should emit a change event with textarea value', () => { | ||
const userInput = 'hello'; | ||
const wrapper = mount( TextArea ); | ||
|
||
wrapper.find( 'textarea' ).setValue( userInput ); | ||
expect( wrapper.emitted( 'input' )![ 0 ] ).toEqual( [ userInput ] ); | ||
} ); | ||
} ); |