Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b1996a6

Browse files
author
Jessica Peng
authoredApr 16, 2020
Merge pull request #13 from MyEtherWallet/feature/storybook
Updates
2 parents 4325385 + 8787c0b commit b1996a6

31 files changed

+525
-137
lines changed
 

‎package-lock.json

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"git-url-parse": "^11.1.2",
2929
"material-design-icons-iconfont": "^5.0.1",
3030
"vue": "^2.6.10",
31+
"vue-cli-plugin-style-resources-loader": "^0.1.4",
3132
"vue-router": "^3.0.3",
3233
"vuetify": "^2.0.0",
3334
"vuex": "^3.0.1"

‎src/assets/styles/basics/_base.scss

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
* {
2+
color: var(--v-info-base);
3+
}
4+
5+
html,
6+
body {
7+
font-family: "Roboto", sans-serif;
8+
}

‎src/assets/styles/basics/_button.scss

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.v-application {
2+
.v-btn {
3+
height: 100% !important;
4+
width: 100%;
5+
}
6+
7+
.disabled-btn {
8+
pointer-events: none;
9+
10+
.icon {
11+
filter: grayscale(100%);
12+
}
13+
}
14+
}

‎src/assets/styles/basics/_input.scss

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
.v-application,
2+
.v-application--is-ltr {
3+
4+
.theme--light.v-text-field--outlined:not(.v-input--is-focused):not(.v-input--has-state) > .v-input__control > .v-input__slot fieldset {
5+
color: var(--v-light-grey-base) !important;
6+
}
7+
8+
.v-input {
9+
input {
10+
color: var(--v-info-base) !important;
11+
}
12+
.v-label {
13+
color: var(--v-input-label-base);
14+
font-weight: 500;
15+
text-transform: capitalize;
16+
}
17+
18+
input::placeholder {
19+
color: var(--v-input-placeholder-base);
20+
}
21+
.v-text-field__suffix {
22+
color: var(--v-input-placeholder-base) !important;
23+
}
24+
25+
.mdi-chevron-down {
26+
color: var(--v-basic-base);
27+
cursor: pointer;
28+
font-size: 20px;
29+
}
30+
}
31+
}

‎src/assets/styles/global.scss

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@import "setup/_typography.min.scss";
2+
3+
@import "basics/_base.scss";
4+
@import "basics/_input.scss";
5+
@import "basics/_button.scss";
6+
7+
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap");

‎src/components/Ad/AdSlider.vue

-61
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
<template>
2+
<div>
3+
<v-combobox
4+
class="address-select"
5+
v-model="addressValue"
6+
:items="items"
7+
item-text="address"
8+
item-value="address"
9+
:label="label"
10+
:placeholder="placeholder"
11+
:menu-props="{ value: autoSelectMenu, closeOnClick: true }"
12+
ref="addressInput"
13+
return-object
14+
outlined
15+
>
16+
<template v-slot:prepend-inner>
17+
<div v-if="!isValidAddress" class="blockie-placeholder"></div>
18+
<div v-if="isValidAddress" class="blockie-container">
19+
<blockie :address="addressValue" width="30px" height="30px" />
20+
</div>
21+
</template>
22+
23+
<template v-slot:append>
24+
<div class="icon-container">
25+
<img
26+
@click="copyToClipboard"
27+
class="copy-icon"
28+
src="@/assets/images/icons/icon-copy-enable.png"
29+
alt="copy"
30+
/>
31+
<img
32+
@click="saveAddress"
33+
class="save-icon"
34+
src="@/assets/images/icons/icon-saved-enable.png"
35+
alt="save"
36+
/>
37+
</div>
38+
<div class="border" />
39+
<v-icon @click="toggle">mdi-chevron-down</v-icon>
40+
</template>
41+
42+
<template v-slot:item="data">
43+
<div class="item-container" @click="selectAddress(data.item)">
44+
<div class="address-container">
45+
<blockie
46+
class="blockie"
47+
:address="data.item.address"
48+
width="30px"
49+
height="30px"
50+
/>
51+
<div class="address">{{ data.item.address }}</div>
52+
</div>
53+
<div class="nickname">{{ data.item.nickname }}</div>
54+
</div>
55+
</template>
56+
</v-combobox>
57+
</div>
58+
</template>
59+
60+
<script>
61+
import Blockie from "@/components/Blockie/Blockie.vue";
62+
63+
export default {
64+
name: "AddressSelector",
65+
props: {
66+
/**
67+
* Returns if the address is valid or not.
68+
*/
69+
isValidAddress: {
70+
type: Boolean,
71+
default: false
72+
},
73+
/**
74+
* The input label.
75+
*/
76+
label: {
77+
type: String,
78+
default: "To Address"
79+
},
80+
/**
81+
* The saved addresses in address book.
82+
*/
83+
items: {
84+
type: Array,
85+
default: function() {
86+
return [];
87+
}
88+
},
89+
/**
90+
* The input placeholder.
91+
*/
92+
placeholder: {
93+
type: String,
94+
default: "Please enter an address"
95+
},
96+
/**
97+
* The function applied to save the address.
98+
*/
99+
saveAddress: {
100+
type: Function,
101+
default: function() {}
102+
}
103+
},
104+
components: {
105+
blockie: Blockie
106+
},
107+
data() {
108+
return {
109+
addressValue: "",
110+
autoSelectMenu: false
111+
};
112+
},
113+
watch: {
114+
inputValue(newValue) {
115+
console.log("input value:", newValue);
116+
}
117+
},
118+
mounted() {
119+
this.addressValue = this.value;
120+
},
121+
methods: {
122+
toggle() {
123+
this.autoSelectMenu = !this.autoSelectMenu;
124+
},
125+
copyToClipboard() {
126+
this.$refs.addressInput.$el.querySelector("input").select();
127+
document.execCommand("copy");
128+
console.log("copied");
129+
// Toast.responseHandler(this.$t('common.copied'), Toast.INFO);
130+
},
131+
selectAddress(data) {
132+
this.autoSelectMenu = false;
133+
this.addressValue = data;
134+
this.$emit("emitSelectedValue", data);
135+
}
136+
}
137+
};
138+
</script>
139+
140+
<style lang="scss" scoped>
141+
.v-application {
142+
.item-container {
143+
align-items: center;
144+
display: flex;
145+
justify-content: space-between;
146+
width: 100%;
147+
148+
.address-container {
149+
align-items: center;
150+
display: flex;
151+
justify-content: space-between;
152+
153+
.blockie {
154+
margin-right: 5px;
155+
}
156+
.address {
157+
color: var(--v-basic-base);
158+
}
159+
}
160+
161+
.nickname {
162+
font-size: 10px;
163+
font-weight: 700;
164+
text-align: right;
165+
color: var(--v-primary-base);
166+
}
167+
}
168+
169+
.address-select {
170+
.blockie-placeholder {
171+
height: 30px;
172+
width: 30px;
173+
border-radius: 100px;
174+
background-color: var(--v-light-grey-base);
175+
margin-bottom: 17px;
176+
margin-right: 5px;
177+
}
178+
179+
.blockie-container {
180+
margin-bottom: 17px;
181+
margin-right: 5px;
182+
max-height: 25px;
183+
}
184+
185+
.icon-container {
186+
.copy-icon {
187+
height: 23px;
188+
margin-right: 5px;
189+
}
190+
191+
.save-icon {
192+
height: 25px;
193+
}
194+
195+
img {
196+
cursor: pointer;
197+
&:hover {
198+
opacity: 0.5;
199+
}
200+
}
201+
}
202+
203+
.border {
204+
border-right: 1px solid var(--v-light-grey-base);
205+
margin: 0 15px;
206+
}
207+
208+
.mdi-chevron-down {
209+
margin-right: 5px;
210+
}
211+
}
212+
}
213+
</style>

‎src/components/Blockie/Blockie.vue

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<template lang="html">
2+
<div ref="identicon" class="address-identicon" />
3+
</template>
4+
<script>
5+
import Blockies from "@/helpers/blockies.js";
6+
export default {
7+
name: "Blockie",
8+
props: {
9+
address: {
10+
type: String,
11+
default: ""
12+
},
13+
width: {
14+
type: String,
15+
default: "64px"
16+
},
17+
height: {
18+
type: String,
19+
default: "64px"
20+
},
21+
size: {
22+
type: Number,
23+
default: 8
24+
},
25+
scale: {
26+
type: Number,
27+
default: 16
28+
}
29+
},
30+
data() {
31+
return {};
32+
},
33+
watch: {
34+
address() {
35+
this.setBlockie();
36+
},
37+
width() {
38+
this.setBlockie();
39+
},
40+
height() {
41+
this.setBlockie();
42+
},
43+
scale() {
44+
this.setBlockie();
45+
},
46+
size() {
47+
this.setBlockie();
48+
}
49+
},
50+
mounted() {
51+
this.setBlockie();
52+
},
53+
methods: {
54+
setBlockie() {
55+
console.error('this', this.address)
56+
const data = Blockies({
57+
seed: this.address ? this.address.toLowerCase() : "",
58+
size: this.size,
59+
scale: this.scale
60+
}).toDataURL();
61+
this.$refs.identicon.style.width = this.width;
62+
this.$refs.identicon.style.height = this.height;
63+
this.$refs.identicon.style.backgroundImage = `url('${data}')`;
64+
}
65+
}
66+
};
67+
</script>
68+
<style lang="scss" scoped>
69+
.address-identicon {
70+
background-repeat: no-repeat;
71+
background-size: cover;
72+
border-radius: 50%;
73+
box-shadow: inset rgba(255, 255, 255, 0.25) 0 2px 2px,
74+
inset rgba(0, 0, 0, 0.6) 0 -1px 8px;
75+
height: 100%;
76+
width: 100%;
77+
}
78+
</style>

‎src/components/MewButton/MewButton.vue

+1-13
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export default {
108108
}
109109
110110
if (this.disabled) {
111-
classes.push("disabled-theme");
111+
classes.push("disabled-btn");
112112
}
113113
114114
if (
@@ -135,14 +135,10 @@ export default {
135135
</script>
136136

137137
<style lang="scss" scoped>
138-
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap");
139-
140138
.v-application {
141139
.v-btn {
142140
border-radius: 6px !important;
143141
padding: 20px;
144-
height: 100%;
145-
width: 100%;
146142
147143
.icon {
148144
height: 27px;
@@ -161,14 +157,6 @@ export default {
161157
background-color: var(--v-white-base) !important;
162158
}
163159
164-
.disabled-theme {
165-
pointer-events: none;
166-
167-
.icon {
168-
filter: grayscale(100%);
169-
}
170-
}
171-
172160
.theme--light.v-btn.v-btn--disabled:not(.v-btn--flat):not(.v-btn--text):not(.v-btn--outlined) {
173161
color: var(--v-white-base) !important;
174162
}

‎src/components/MewInput/MewInput.vue

+2-23
Original file line numberDiff line numberDiff line change
@@ -114,29 +114,8 @@ export default {
114114
};
115115
</script>
116116

117-
<style lang="scss">
118-
.v-application,
119-
.v-application--is-ltr {
120-
.theme--light.v-label {
121-
color: var(--v-input-label-base);
122-
font-weight: 500;
123-
text-transform: capitalize;
124-
}
125-
126-
.v-input {
127-
input::placeholder {
128-
color: var(--v-input-placeholder-base);
129-
}
130-
131-
.v-text-field__suffix {
132-
color: var(--v-input-placeholder-base) !important;
133-
}
134-
135-
.v-text-field--outlined fieldset:before {
136-
border: 1px solid var(--v-form-base);
137-
}
138-
}
139-
117+
<style lang="scss" scoped>
118+
.v-application {
140119
.search-input {
141120
fieldset {
142121
background-color: var(--v-primary-silver-base);

‎src/components/MewSelect/MewSelect.vue

+7-20
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<template>
22
<div>
33
<v-select
4+
class="mew-select"
45
color="basic"
56
append-icon="mdi-chevron-down"
67
:disabled="disabled"
@@ -11,7 +12,7 @@
1112
v-model="selectModel"
1213
outlined
1314
>
14-
<template slot="item" slot-scope="data">
15+
<template v-slot:item="data">
1516
<img class="item-img" v-if="data.item.img" :src="data.item.img" />{{
1617
data.item.name ? data.item.name : data.item
1718
}}
@@ -72,27 +73,13 @@ export default {
7273
};
7374
</script>
7475

75-
<style lang="scss">
76+
<style lang="scss" scoped>
7677
.v-application {
77-
.theme--light.v-label {
78-
color: var(--v-input-label-base);
79-
font-weight: 500;
80-
text-transform: capitalize;
81-
}
82-
83-
.v-input {
84-
input::placeholder {
85-
color: var(--v-input-placeholder-base);
78+
.mew-select {
79+
.item-img {
80+
margin-right: 5px;
81+
max-height: 25px;
8682
}
8783
}
88-
89-
.v-text-field--outlined fieldset:before {
90-
border: 1px solid var(--v-form-base);
91-
}
92-
93-
.item-img {
94-
margin-right: 5px;
95-
max-height: 25px;
96-
}
9784
}
9885
</style>

‎src/components/MewSuperButton/MewSuperButton.vue

+4-15
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<div class="body-2" v-if="titleIcon">
1717
<img
1818
v-if="titleIcon"
19-
class="title-icon"
19+
class="icon title-icon"
2020
:src="titleIcon"
2121
alt="Icon"
2222
/>
@@ -35,7 +35,7 @@
3535
</div>
3636
<img
3737
v-if="rightIcon"
38-
class="right-icon"
38+
class="icon right-icon"
3939
:src="rightIcon"
4040
alt="Icon"
4141
/>
@@ -119,7 +119,7 @@ export default {
119119
}
120120
121121
if (this.disabled) {
122-
classes.push("disabled");
122+
classes.push("disabled-btn");
123123
}
124124
125125
if (this.active && !this.disabled) {
@@ -136,21 +136,10 @@ export default {
136136
.v-application {
137137
.v-btn {
138138
border-radius: 12px;
139-
height: 100% !important;
140-
width: 100%;
141139
}
142140
143-
.disabled {
141+
.disabled-btn {
144142
color: var(--v-disabled-super-base) !important;
145-
pointer-events: none;
146-
147-
.right-icon {
148-
filter: grayscale(100%);
149-
}
150-
151-
.title-icon {
152-
filter: grayscale(100%);
153-
}
154143
}
155144
156145
.green-border {

‎src/App.vue ‎src/delete/App.vue

-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export default {
2323
</script>
2424

2525
<style lang="scss">
26-
@import "@/global.scss";
2726
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap");
2827
2928
* {
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

‎src/helpers/blockies.js

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
const randseed = new Array(4); // Xorshift: [x, y, z, w] 32 bit values
2+
3+
function seedrand(seed) {
4+
for (let i = 0; i < randseed.length; i++) {
5+
randseed[i] = 0;
6+
}
7+
for (let j = 0; j < seed.length; j++) {
8+
randseed[j % 4] =
9+
(randseed[j % 4] << 5) - randseed[j % 4] + seed.charCodeAt(j);
10+
}
11+
}
12+
13+
function rand() {
14+
// based on Java's String.hashCode(), expanded to 4 32bit values
15+
const t = randseed[0] ^ (randseed[0] << 11);
16+
17+
randseed[0] = randseed[1];
18+
randseed[1] = randseed[2];
19+
randseed[2] = randseed[3];
20+
randseed[3] = randseed[3] ^ (randseed[3] >> 19) ^ t ^ (t >> 8);
21+
22+
return (randseed[3] >>> 0) / ((1 << 31) >>> 0);
23+
}
24+
25+
function createColor() {
26+
// saturation is the whole color spectrum
27+
const h = Math.floor(rand() * 360);
28+
// saturation goes from 40 to 100, it avoids greyish colors
29+
const s = rand() * 60 + 40 + "%";
30+
// lightness can be anything from 0 to 100, but probabilities are a bell curve around 50%
31+
const l = (rand() + rand() + rand() + rand()) * 25 + "%";
32+
33+
const color = "hsl(" + h + "," + s + "," + l + ")";
34+
return color;
35+
}
36+
37+
function createImageData(size) {
38+
const width = size; // Only support square icons for now
39+
const height = size;
40+
41+
const dataWidth = Math.ceil(width / 2);
42+
const mirrorWidth = width - dataWidth;
43+
44+
const data = [];
45+
for (let y = 0; y < height; y++) {
46+
let row = [];
47+
for (let x = 0; x < dataWidth; x++) {
48+
// this makes foreground and background color to have a 43% (1/2.3) probability
49+
// spot color has 13% chance
50+
row[x] = Math.floor(rand() * 2.3);
51+
}
52+
const r = row.slice(0, mirrorWidth);
53+
r.reverse();
54+
row = row.concat(r);
55+
56+
for (let i = 0; i < row.length; i++) {
57+
data.push(row[i]);
58+
}
59+
}
60+
61+
return data;
62+
}
63+
64+
function createCanvas(imageData, color, scale, bgcolor, spotcolor) {
65+
const width = Math.sqrt(imageData.length);
66+
const c = document.createElement("canvas");
67+
c.width = c.height = width * scale;
68+
const cc = c.getContext("2d");
69+
cc.fillStyle = bgcolor;
70+
cc.fillRect(0, 0, c.width, c.height);
71+
cc.fillStyle = color;
72+
73+
for (let i = 0; i < imageData.length; i++) {
74+
const row = Math.floor(i / width);
75+
const col = i % width;
76+
cc.fillStyle = imageData[i] === 1 ? color : spotcolor;
77+
if (imageData[i]) {
78+
cc.fillRect(col * scale, row * scale, scale, scale);
79+
}
80+
}
81+
return c;
82+
}
83+
84+
function createIcon(opts) {
85+
opts = opts || {};
86+
const size = opts.size || 8;
87+
const scale = opts.scale || 4;
88+
const seed =
89+
opts.seed || Math.floor(Math.random() * Math.pow(10, 16)).toString(16);
90+
seedrand(seed);
91+
const color = opts.color || createColor();
92+
const bgcolor = opts.bgcolor || createColor();
93+
const spotcolor = opts.spotcolor || createColor();
94+
const imageData = createImageData(size);
95+
const canvas = createCanvas(imageData, color, scale, bgcolor, spotcolor);
96+
97+
return canvas;
98+
}
99+
export default createIcon;

‎src/main.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import Vue from "vue";
2-
import App from "./App.vue";
32
import router from "./router";
43
import store from "./store";
54
import vuetify from "./plugins/vuetify";

‎src/plugins/vuetify.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Vue from "vue";
22
import "@mdi/font/css/materialdesignicons.css"; // Ensure you are using css-loader
3+
import "@/assets/styles/global.scss";
34

45
import Vuetify, {
56
VApp,
@@ -27,7 +28,8 @@ import Vuetify, {
2728
VToolbar,
2829
VTextField,
2930
VLayout,
30-
VSelect
31+
VSelect,
32+
VCombobox
3133
} from "vuetify/lib";
3234

3335
Vue.use(Vuetify, {
@@ -57,7 +59,8 @@ Vue.use(Vuetify, {
5759
VToolbar,
5860
VTextField,
5961
VLayout,
60-
VSelect
62+
VSelect,
63+
VCombobox
6164
}
6265
});
6366

@@ -99,7 +102,6 @@ export default new Vuetify({
99102
"dark-blue": "#184f90",
100103
"light-grey": "#e0e0e0",
101104
"input-label": "#6d89a6",
102-
form: "#cecece",
103105
"input-placeholder": "#96a8b6",
104106
"select-border": "#a3b7cf",
105107
"primary-hover": "#0BAA93",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { withKnobs, text, array, boolean } from "@storybook/addon-knobs";
2+
import mewAddressSelector from "@/components/AddressSelect/AddressSelect.vue";
3+
4+
export default {
5+
title: "AddressSelector",
6+
parameters: {
7+
component: AddressSelector
8+
},
9+
decorators: [withKnobs]
10+
};
11+
12+
const addressesArray = [
13+
{
14+
address: "0xDECAF9CD2367cdbb726E904cD6397eDFcAe6068D",
15+
currency: "ETH",
16+
nickname: "nickname",
17+
resolverAddr: "0xDECAF9CD2367cdbb726E904cD6397eDFcAe6068D"
18+
}
19+
];
20+
21+
export const AddressSelector = () => ({
22+
components: { "address-selector": mewAddressSelector },
23+
props: {
24+
label: {
25+
default: text("label", "To Address")
26+
},
27+
items: {
28+
default: array("addresses", addressesArray)
29+
},
30+
placeholder: {
31+
default: text("placeholder", "Please enter an address")
32+
},
33+
isValidAddress: {
34+
default: boolean("is-valid-address", false)
35+
}
36+
},
37+
template: `
38+
<div>
39+
<br />
40+
<address-selector @emitSelectedValue="getSelectedValue" :is-valid-address="isValidAddress" :label="label" :items="items">
41+
<template v-slot:blockie>
42+
</template>
43+
</address-selector>
44+
</div>`,
45+
methods: {
46+
getSelectedValue(value) {
47+
console.log("selected value:", value);
48+
}
49+
}
50+
});

0 commit comments

Comments
 (0)
Please sign in to comment.