Masked input (#4759)

* create masked-input component
This commit is contained in:
madalynrose 2018-06-14 14:52:00 -04:00 committed by GitHub
parent 75eb0f862e
commit 9fb8be5a72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 296 additions and 43 deletions

View File

@ -5,8 +5,10 @@ const { computed } = Ember;
const GLYPHS_WITH_SVG_TAG = [
'folder',
'file',
'hidden',
'perf-replication',
'role',
'visible',
'information-reversed',
'true',
'false',

View File

@ -0,0 +1,51 @@
import Ember from 'ember';
const { computed } = Ember;
import autosize from 'autosize';
export default Ember.Component.extend({
value: null,
didInsertElement(){
this._super(...arguments);
autosize(this.element.querySelector('textarea'));
},
didUpdate(){
this._super(...arguments);
autosize.update(this.element.querySelector('textarea'));
},
willDestroyElement(){
this._super(...arguments);
autosize.destroy(this.element.querySelector('textarea'));
},
shouldObscure: computed("isMasked", "isFocused", "value", function(){
if(this.get('value') === "" ){
return false;
}
if(this.get('isFocused') === true){
return false;
}
return this.get('isMasked');
}),
displayValue: computed("shouldObscure", function(){
if(this.get("shouldObscure")){
return "■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■";
}
else{
return this.get('value');
}
}),
isMasked: true,
isFocused: false,
displayOnly: false,
onKeyDown(){},
onChange(){},
actions: {
toggleMask(){
this.toggleProperty('isMasked');
},
updateValue(e){
this.set('value', e.target.value);
this.onChange();
},
}
});

View File

@ -1,7 +1,6 @@
import Ember from 'ember';
import FocusOnInsertMixin from 'vault/mixins/focus-on-insert';
import keys from 'vault/lib/keycodes';
import autosize from 'autosize';
import KVObject from 'vault/lib/kv-object';
const LIST_ROUTE = 'vault.cluster.secrets.backend.list';
@ -51,13 +50,6 @@ export default Ember.Component.extend(FocusOnInsertMixin, {
}
},
didRender() {
const textareas = this.$('textarea');
if (textareas.length) {
autosize(textareas);
}
},
willDestroyElement() {
const key = this.get('key');
if (get(key, 'isError') && !key.isDestroyed) {
@ -164,7 +156,7 @@ export default Ember.Component.extend(FocusOnInsertMixin, {
},
actions: {
handleKeyDown(_, e) {
handleKeyDown(e) {
e.stopPropagation();
if (!(e.keyCode === keys.ENTER && e.metaKey)) {
return;

View File

@ -1,5 +1,5 @@
.console-ui-panel-scroller {
background: linear-gradient(to right, #191A1C, #1B212D);
background: linear-gradient(to right, #191a1c, #1b212d);
height: 0;
left: 0;
overflow: auto;
@ -24,8 +24,8 @@
font-weight: $font-weight-semibold;
transition: justify-content $speed ease-in;
pre, p {
pre,
p {
background: none;
color: inherit;
font-size: $body-size;
@ -42,8 +42,7 @@
padding: $size-8 $size-4;
}
.button,
{
.button {
background: transparent;
border: none;
color: $grey-dark;
@ -130,19 +129,21 @@
}
.panel-open {
.navbar, .navbar-sections{
.navbar,
.navbar-sections {
transition: transform $speed ease-in;
}
}
.panel-open.panel-fullscreen {
.navbar, .navbar-sections{
.navbar,
.navbar-sections {
transform: translate3d(0, -100px, 0);
}
}
.page-container > header {
background: linear-gradient(to right, #191A1C, #1B212D);
background: linear-gradient(to right, #191a1c, #1b212d);
}
header .navbar,

View File

@ -5,7 +5,7 @@
bottom: 0;
left: 0;
margin: 10px;
z-index: 1;
z-index: 300;
.notification {
box-shadow: $box-shadow-high;

View File

@ -0,0 +1,72 @@
.masked-input {
position: relative;
}
.masked-input .masked-value {
padding-left: 2.50rem;
}
// we want to style the boxes the same everywhere so they
// need to be the same font and small
.masked-input.masked .masked-value {
font-size: 9px;
font-family: $family-primary;
}
.masked-input.masked .masked-value {
line-height: 2.5;
}
// aligns the boxes on the input page
.masked-input.masked:not(.display-only) .masked-value {
line-height: 3;
}
//override bulma's pre styling
.masked-input .display-only {
line-height: 1.5;
font-size: 1rem;
}
.masked-input-toggle {
background: transparent;
position: absolute;
height: auto;
top: $size-6/4;
bottom: $size-6/4;
left: 1px;
line-height: 1rem;
min-width: 0;
max-height: 2rem;
padding: 0 $size-8;
z-index: 100;
border: 0;
box-shadow: none;
color: $blue;
&:active,
&.is-active,
&:focus,
&.is-focused,
&:hover,
&:focus:not(:active) {
color: $blue;
border: 0;
box-shadow: none;
}
}
.masked-input.display-only .masked-input-toggle {
top: 0;
font-size: 0.5rem;
height: 1rem;
padding-left: 0;
}
.masked-input .input:focus + .masked-input-toggle {
background: rgba($white, 0.95);
}
.masked-input.masked .masked-value,
.masked-input.masked .masked-input-toggle {
color: $grey-light;
}

View File

@ -57,6 +57,7 @@
@import "./components/list-pagination";
@import "./components/loader";
@import "./components/login-form";
@import "./components/masked-input";
@import "./components/message-in-page";
@import "./components/page-header";
@import "./components/popup-menu";

View File

@ -64,10 +64,14 @@ $radius: 2px;
//box
$box-radius: 0;
$box-shadow: 0 0 0 1px rgba($black, 0.1);
$box-shadow-low: 0 5px 1px -2px rgba($black, 0.12), 0 3px 2px -1px rgba($black, 0);
$box-shadow-middle: 0 8px 4px -4px rgba($black, 0.10), 0 6px 8px -2px rgba($black, 0.05);
$box-shadow-high: 0 12px 5px -7px rgba($black, 0.08), 0 11px 10px -3px rgba($black, 0.10);
$box-shadow-highest: 0 16px 6px -10px rgba($black, 0.06), 0 16px 16px -4px rgba($black, 0.20);
$box-shadow-low: 0 5px 1px -2px rgba($black, 0.12),
0 3px 2px -1px rgba($black, 0);
$box-shadow-middle: 0 8px 4px -4px rgba($black, 0.10),
0 6px 8px -2px rgba($black, 0.05);
$box-shadow-high: 0 12px 5px -7px rgba($black, 0.08),
0 11px 10px -3px rgba($black, 0.10);
$box-shadow-highest: 0 16px 6px -10px rgba($black, 0.06),
0 16px 16px -4px rgba($black, 0.20);
$link: $blue;
$text: $black;

View File

@ -0,0 +1,21 @@
<div class="masked-input {{if shouldObscure "masked"}} {{if displayOnly "display-only"}}" data-test-masked-input>
{{#if displayOnly}}
<pre class="masked-value display-only">{{displayValue}}</pre>
{{else}}
<textarea
class="input masked-value"
rows=1
wrap="off"
placeholder="value"
onfocus={{action (mut isFocused) true}}
onblur={{action (mut isFocused) false}}
onkeydown={{action onKeyDown}}
onchange={{action "updateValue"}}
value={{readonly displayValue}}
data-test-textarea
/>
{{/if}}
<button {{action "toggleMask"}} class="{{if (eq value "") "has-text-grey"}} masked-input-toggle button is-compact" data-test-button>
{{i-con glyph=(if shouldObscure "hidden" "visible") aria-hidden="true" size=16}}
</button>
</div>

View File

@ -18,16 +18,12 @@
}}
</div>
<div class="column info-table-row-edit">
{{textarea
{{masked-input
data-test-secret-value=true
name=secret.name
key-down="handleKeyDown"
change="handleChange"
onKeyDown=(action "handleKeyDown")
onChange=(action "handleChange")
value=secret.value
wrap="off"
class="input"
placeholder="value"
rows=1
}}
</div>
<div class="column is-narrow info-table-row-edit">

View File

@ -17,6 +17,8 @@
</div>
</div>
{{#each-in key.secretData as |key value|}}
{{info-table-row label=key value=value alwaysRender=true}}
{{#info-table-row label=key value=value alwaysRender=true}}
{{masked-input value=value displayOnly=true}}
{{/info-table-row}}
{{/each-in}}
{{/if}}

View File

@ -0,0 +1,3 @@
<svg width={{size}} height={{size}} viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M8,12 C5.05448133,12 2.38781467,10.6666667 0,8 C2.38781467,5.33333333 5.05448133,4 8,4 C10.9455187,4 13.6121853,5.33333333 16,8 C13.6121853,10.6666667 10.9455187,12 8,12 Z M8,5 C5.60667919,5 3.41034085,5.98473247 1.37808575,8 C3.41034085,10.0152675 5.60667919,11 8,11 C10.3933208,11 12.5896591,10.0152675 14.6219143,8 C12.5896591,5.98473247 10.3933208,5 8,5 Z M6.08844608,10.9323692 L3.20710678,13.8137085 L2.5,13.1066017 L5.3362082,10.2703935 C4.8147799,9.65920414 4.5,8.86636246 4.5,8 C4.5,6.06700338 6.06700338,4.5 8,4.5 C8.86636246,4.5 9.65920414,4.8147799 10.2703935,5.3362082 L13.1066017,2.5 L13.8137085,3.20710678 L10.9323692,6.08844608 C11.2913366,6.63798846 11.5,7.29462625 11.5,8 C11.5,9.93299662 9.93299662,11.5 8,11.5 C7.29462625,11.5 6.63798846,11.2913366 6.08844608,10.9323692 Z M6.81756939,10.2032459 C7.16962006,10.3925802 7.57226541,10.5 8,10.5 C9.38071187,10.5 10.5,9.38071187 10.5,8 C10.5,7.57226541 10.3925802,7.16962006 10.2032459,6.81756939 L6.81756939,10.2032459 Z M6.04644524,9.56015648 L9.56015648,6.04644524 C9.13251828,5.70447638 8.59013815,5.5 8,5.5 C6.61928813,5.5 5.5,6.61928813 5.5,8 C5.5,8.59013815 5.70447638,9.13251828 6.04644524,9.56015648 Z" id="path-1"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,3 @@
<svg width={{size}} height={{size}} viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M10.2290316,5.30146576 C11.0053403,5.94343599 11.5,6.91395101 11.5,8 C11.5,9.08604899 11.0053403,10.056564 10.2290316,10.6985342 C11.7764341,10.2677626 13.2372598,9.37308018 14.6219143,8 C13.2372598,6.62691982 11.7764341,5.73223742 10.2290316,5.30146576 Z M5.77096844,10.6985342 C4.99465975,10.056564 4.5,9.08604899 4.5,8 C4.5,6.91395101 4.99465975,5.94343599 5.77096844,5.30146576 C4.22356585,5.73223742 2.76274022,6.62691982 1.37808575,8 C2.76274022,9.37308018 4.22356585,10.2677626 5.77096844,10.6985342 Z M8,12 C5.05448133,12 2.38781467,10.6666667 0,8 C2.38781467,5.33333333 5.05448133,4 8,4 C10.9455187,4 13.6121853,5.33333333 16,8 C13.6121853,10.6666667 10.9455187,12 8,12 Z M8.9651005,7.74939083 C9.51704882,7.76866529 9.98011637,7.33684781 9.99939083,6.7848995 C10.0186653,6.23295118 9.58684781,5.76988363 9.0348995,5.75060917 C8.48295118,5.73133471 8.01988363,6.16315219 8.00060917,6.7151005 C7.98133471,7.26704882 8.41315219,7.73011637 8.9651005,7.74939083 Z" id="path-1"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -33,7 +33,7 @@
},
"devDependencies": {
"Duration.js": "icholy/Duration.js#golang_compatible",
"autosize": "3.0.17",
"autosize": "^4.0.0",
"babel-plugin-transform-object-rest-spread": "^6.23.0",
"base64-js": "1.2.1",
"broccoli-asset-rev": "^2.4.5",

View File

@ -8,7 +8,7 @@ moduleForAcceptance('Acceptance | console', {
},
});
test("refresh reloads the current route's data", function(assert) {
test('refresh reloads the current route\'s data', function(assert) {
let numEngines;
enginesPage.visit();
andThen(() => {

View File

@ -0,0 +1,95 @@
import { moduleForComponent, test } from 'ember-qunit';
import { create } from 'ember-cli-page-object';
import hbs from 'htmlbars-inline-precompile';
import maskedInput from 'vault/tests/pages/components/masked-input';
const component = create(maskedInput);
moduleForComponent('masked-input', 'Integration | Component | masked input', {
integration: true,
beforeEach() {
component.setContext(this);
},
afterEach() {
component.removeContext();
},
});
const hasClass = (classString = '', classToFind) => {
return classString.split(' ').contains(classToFind);
}
test('it renders', function(assert) {
this.render(hbs`{{masked-input}}`);
assert.ok(hasClass(component.wrapperClass, 'masked'));
});
test('it renders a textarea', function(assert) {
this.render(hbs`{{masked-input}}`);
assert.ok(component.textareaIsPresent);
});
test('it does not render a textarea when displayOnly is true', function(assert) {
this.render(hbs`{{masked-input displayOnly=true}}`);
assert.notOk(component.textareaIsPresent);
});
test('it unmasks text on focus', function(assert) {
this.set('value', 'value');
this.render(hbs`{{masked-input value=value}}`);
assert.ok(hasClass(component.wrapperClass, 'masked'));
component.focus();
assert.notOk(hasClass(component.wrapperClass, 'masked'));
});
test('it remasks text on blur', function(assert) {
this.set('value', 'value');
this.render(hbs`{{masked-input value=value}}`);
assert.ok(hasClass(component.wrapperClass, 'masked'));
component.focus();
component.blur();
assert.ok(hasClass(component.wrapperClass, 'masked'));
});
test('it unmasks text when button is clicked', function(assert) {
this.set('value', 'value');
this.render(hbs`{{masked-input value=value}}`);
assert.ok(hasClass(component.wrapperClass, 'masked'));
component.toggleMasked();
assert.notOk(hasClass(component.wrapperClass, 'masked'));
});
test('it remasks text when button is clicked', function(assert) {
this.set('value', 'value');
this.render(hbs`{{masked-input value=value}}`);
component.toggleMasked();
component.toggleMasked();
assert.ok(hasClass(component.wrapperClass, 'masked'));
});

View File

@ -0,0 +1,10 @@
import { attribute, clickable, fillable, focusable, blurrable, isPresent } from 'ember-cli-page-object';
export default {
wrapperClass: attribute('class','[data-test-masked-input]'),
enterText: fillable('[data-test-textarea]'),
textareaIsPresent: isPresent('[data-test-textarea]'),
toggleMasked: clickable('[data-test-button]'),
focus: focusable('[data-test-textarea]'),
blur: blurrable('[data-test-textarea]')
};

View File

@ -443,9 +443,9 @@ autoprefixer@^7.0.0:
postcss "^6.0.17"
postcss-value-parser "^3.2.3"
autosize@3.0.17:
version "3.0.17"
resolved "https://registry.yarnpkg.com/autosize/-/autosize-3.0.17.tgz#f5a2cd35b96d864634cffc600c8c628c658d805b"
autosize@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/autosize/-/autosize-4.0.2.tgz#073cfd07c8bf45da4b9fd153437f5bafbba1e4c9"
aws-sign2@~0.6.0:
version "0.6.0"