瀏覽代碼

feat: new mui route loader and basic sass components

hover-contact
ntasicc 3 年之前
父節點
當前提交
c21b86a884
共有 74 個檔案被更改,包括 5207 行新增96 行删除
  1. 10
    0
      assets/images/chevron-down.svg
  2. 12
    0
      assets/images/down.svg
  3. 6
    0
      assets/images/svg/caps-lock.svg
  4. 10
    0
      assets/images/svg/eye-off.svg
  5. 4
    0
      assets/images/svg/eye-on.svg
  6. 10
    0
      assets/images/svg/search.svg
  7. 46
    0
      assets/styles/_base.scss
  8. 7
    0
      assets/styles/_functions.scss
  9. 17
    0
      assets/styles/_layout.scss
  10. 81
    0
      assets/styles/_mixins.scss
  11. 244
    0
      assets/styles/_overwrite.scss
  12. 127
    0
      assets/styles/_reset.scss
  13. 57
    0
      assets/styles/_typography.scss
  14. 39
    0
      assets/styles/_utility.scss
  15. 72
    0
      assets/styles/_variables.scss
  16. 60
    0
      assets/styles/components/_app-button.scss
  17. 45
    0
      assets/styles/components/_auth-card.scss
  18. 23
    0
      assets/styles/components/_auth.scss
  19. 173
    0
      assets/styles/components/_button.scss
  20. 46
    0
      assets/styles/components/_error-page.scss
  21. 23
    0
      assets/styles/components/_forgot-password.scss
  22. 7
    0
      assets/styles/components/_icon-button.scss
  23. 479
    0
      assets/styles/components/_input.scss
  24. 72
    0
      assets/styles/components/_loader.scss
  25. 31
    0
      assets/styles/components/_login-card.scss
  26. 72
    0
      assets/styles/components/_login.scss
  27. 169
    0
      assets/styles/components/_modal.scss
  28. 29
    0
      assets/styles/components/_radio.scss
  29. 55
    0
      components/loader/route-loader/CircularIndeterminate.jsx
  30. 5
    0
      components/loader/route-loader/CircularIndeterminate.mock.js
  31. 20
    0
      components/loader/route-loader/CircularIndeterminate.stories.jsx
  32. 93
    0
      components/sass-components/Button/Button.js
  33. 32
    0
      components/sass-components/IconButton/IconButton.js
  34. 187
    0
      components/sass-components/InputFields/BaseInputField.js
  35. 40
    0
      components/sass-components/InputFields/Checkbox.js
  36. 123
    0
      components/sass-components/InputFields/CurrencyField.js
  37. 33
    0
      components/sass-components/InputFields/EmailField.js
  38. 74
    0
      components/sass-components/InputFields/NumberField.js
  39. 74
    0
      components/sass-components/InputFields/PasswordField.js
  40. 130
    0
      components/sass-components/InputFields/PasswordStrength.js
  41. 45
    0
      components/sass-components/InputFields/PercentageField.js
  42. 49
    0
      components/sass-components/InputFields/PhoneNumberField.js
  43. 54
    0
      components/sass-components/InputFields/Radio.js
  44. 37
    0
      components/sass-components/InputFields/Search.js
  45. 122
    0
      components/sass-components/InputFields/SelectField.js
  46. 72
    0
      components/sass-components/InputFields/TextField.js
  47. 26
    0
      components/sass-components/Loader/BlockSectionLoader.js
  48. 11
    0
      components/sass-components/Loader/FullPageLoader.js
  49. 20
    0
      components/sass-components/Loader/SectionLoader.js
  50. 13
    0
      components/sass-components/Section/Section.js
  51. 46
    0
      components/sass-components/styles/_base.scss
  52. 7
    0
      components/sass-components/styles/_functions.scss
  53. 17
    0
      components/sass-components/styles/_layout.scss
  54. 81
    0
      components/sass-components/styles/_mixins.scss
  55. 244
    0
      components/sass-components/styles/_overwrite.scss
  56. 127
    0
      components/sass-components/styles/_reset.scss
  57. 57
    0
      components/sass-components/styles/_typography.scss
  58. 39
    0
      components/sass-components/styles/_utility.scss
  59. 72
    0
      components/sass-components/styles/_variables.scss
  60. 60
    0
      components/sass-components/styles/components/_app-button.scss
  61. 45
    0
      components/sass-components/styles/components/_auth-card.scss
  62. 23
    0
      components/sass-components/styles/components/_auth.scss
  63. 173
    0
      components/sass-components/styles/components/_button.scss
  64. 46
    0
      components/sass-components/styles/components/_error-page.scss
  65. 23
    0
      components/sass-components/styles/components/_forgot-password.scss
  66. 7
    0
      components/sass-components/styles/components/_icon-button.scss
  67. 479
    0
      components/sass-components/styles/components/_input.scss
  68. 72
    0
      components/sass-components/styles/components/_loader.scss
  69. 31
    0
      components/sass-components/styles/components/_login-card.scss
  70. 72
    0
      components/sass-components/styles/components/_login.scss
  71. 169
    0
      components/sass-components/styles/components/_modal.scss
  72. 29
    0
      components/sass-components/styles/components/_radio.scss
  73. 2
    33
      pages/_app.js
  74. 0
    63
      styles/globals.css

+ 10
- 0
assets/images/chevron-down.svg 查看文件

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Master" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
<g id="Icons" transform="translate(-40.000000, -151.000000)" stroke="#000000">
<g id="Icons-/-Chevron-/-Down" transform="translate(40.000000, 151.000000)">
<polyline id="Path" transform="translate(8.000000, 8.000000) rotate(-270.000000) translate(-8.000000, -8.000000) " points="5 2 11 8 5 14" stroke="#e2930a"></polyline>
</g>
</g>
</g>
</svg>

+ 12
- 0
assets/images/down.svg 查看文件

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Master" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Buy-page" transform="translate(-136.000000, -852.000000)" stroke="currentColor">
<g id="Filter-item-Copy-16" transform="translate(136.000000, 848.000000)">
<g id="Icons-/-Checkbox-/-off" transform="translate(0.000000, 4.000000)">
<polyline id="Path" transform="translate(8.000000, 8.000000) rotate(-270.000000) translate(-8.000000, -8.000000) " points="5 2 11 8 5 14"></polyline>
</g>
</g>
</g>
</g>
</svg>

+ 6
- 0
assets/images/svg/caps-lock.svg 查看文件

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Caps-Lock" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M17,19 L17,21 L7,21 L7,19 L17,19 Z M12,3 L21,10 L17,10 L17,17 L7,17 L7,10 L3,10 L12,3 Z" id="Combined-Shape" fill="currentColor"></path>
</g>
</svg>

+ 10
- 0
assets/images/svg/eye-off.svg 查看文件

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Master" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Icons" transform="translate(-40.000000, -79.000000)" fill="currentColor">
<g id="eye-off-outline" transform="translate(40.000000, 79.000000)">
<path d="M2.47398116,3.99464566 L2.53252547,4.02549094 L13.8658588,11.3588243 C14.0358755,11.4688351 14.0845198,11.6958421 13.9745091,11.8658588 C13.8767217,12.0169847 13.6864928,12.0722116 13.5260188,12.0053543 L13.4674745,11.9745091 L2.1341412,4.64117572 C1.96412454,4.53116495 1.91548016,4.30415786 2.02549094,4.1341412 C2.1232783,3.98301528 2.31350719,3.92778845 2.47398116,3.99464566 Z M3.46302885,6.35365053 L4.1083929,6.7708723 C3.81029658,7.0585608 3.52305647,7.38077325 3.2494004,7.73654755 L3.04674798,8.0096561 L3.08366782,8.06547524 C4.27326632,9.84797951 6.06094208,10.962963 7.98004454,10.962963 C8.65722341,10.962963 9.32390587,10.8201119 9.95521088,10.5562937 L10.69564,11.0338727 C9.84843225,11.4648639 8.92701525,11.7037037 7.98004454,11.7037037 C5.75860617,11.7037037 3.72666166,10.4226157 2.40821861,8.41164019 C2.24142826,8.1600981 2.24649075,7.83544344 2.42215728,7.58739593 C2.74871545,7.13128497 3.09707817,6.71935421 3.46302885,6.35365053 Z M5.71548132,7.81273581 L6.50413753,8.32383034 C6.65572725,8.9862843 7.26257495,9.48148148 7.98812175,9.48148148 C8.07995333,9.48148148 8.1698834,9.47354859 8.25722954,9.45834774 L9.04753326,9.96841802 C8.73098269,10.1305066 8.37054437,10.2222222 7.98812175,10.2222222 C6.72856811,10.2222222 5.70749814,9.22729944 5.70749814,8 C5.70749814,7.93693045 5.7101946,7.87447455 5.71548132,7.81273581 Z M7.98004454,4.2962963 C9.10605419,4.2962963 10.2063142,4.63943012 11.2089405,5.27021664 C12.1313806,5.85055521 12.9459997,6.66069361 13.5686244,7.59770297 C13.7306028,7.84293225 13.7306028,8.15776219 13.569353,8.40188399 C13.2688682,8.86037696 12.9288642,9.28189795 12.5567195,9.65936669 L11.9125299,9.24126945 C12.2874179,8.87549361 12.6294637,8.45971211 12.9293712,8.00210112 C12.9300086,8.00103291 12.9300086,7.99966153 12.9300086,7.99944169 C11.736763,6.20380333 9.88220431,5.03703704 7.98004454,5.03703704 C7.33011363,5.03703704 6.68048528,5.17600946 6.04999663,5.44731057 L5.31648269,4.97253171 C6.16987895,4.52781301 7.07030912,4.2962963 7.98004454,4.2962963 Z M7.98812175,5.77777778 C9.24767539,5.77777778 10.2687454,6.77270056 10.2687454,8 C10.2687454,8.05834477 10.2664378,8.11616438 10.2619067,8.1733769 L9.4680009,7.65873003 C9.30983274,7.00504358 8.70728744,6.51851852 7.98812175,6.51851852 C7.90245898,6.51851852 7.81845084,6.52542142 7.73665123,6.53868749 L6.94186885,6.02489746 C7.25523458,5.8669741 7.61098796,5.77777778 7.98812175,5.77777778 Z" id="Combined-Shape"></path>
</g>
</g>
</g>
</svg>

+ 4
- 0
assets/images/svg/eye-on.svg 查看文件

@@ -0,0 +1,4 @@
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.98049 4.29626C5.93884 4.29626 3.94406 5.4623 2.4226 7.58736C2.24693 7.83541 2.24187 8.16007 2.40866 8.41161C3.7271 10.4226 5.75905 11.7037 7.98049 11.7037C10.1878 11.7037 12.2564 10.406 13.5698 8.40185C13.731 8.15773 13.731 7.8429 13.5691 7.59767C12.9464 6.66066 12.1318 5.85052 11.2094 5.27018C10.2068 4.6394 9.1065 4.29626 7.98049 4.29626ZM7.98049 5.03701C9.88265 5.03701 11.7372 6.20377 12.9305 7.99941C12.9305 7.99963 12.9305 8.001 12.9298 8.00207C11.746 9.80837 9.90566 10.9629 7.98049 10.9629C6.06138 10.9629 4.27371 9.84795 3.08411 8.06544L3.04719 8.00962C4.43378 6.07295 6.20601 5.03701 7.98049 5.03701Z" fill="currentColor"/>
<path d="M7.98765 5.77771C6.7281 5.77771 5.70703 6.77263 5.70703 7.99993C5.70703 9.22723 6.7281 10.2222 7.98765 10.2222C9.24721 10.2222 10.2683 9.22723 10.2683 7.99993C10.2683 6.77263 9.24721 5.77771 7.98765 5.77771ZM7.98765 6.51845C8.82736 6.51845 9.50807 7.18173 9.50807 7.99993C9.50807 8.81813 8.82736 9.48141 7.98765 9.48141C7.14795 9.48141 6.46724 8.81813 6.46724 7.99993C6.46724 7.18173 7.14795 6.51845 7.98765 6.51845Z" fill="currentColor"/>
</svg>

+ 10
- 0
assets/images/svg/search.svg 查看文件

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Master" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Icons" transform="translate(-136.000000, -79.000000)" stroke="currentColor">
<g id="Icons/search" transform="translate(136.000000, 79.000000)">
<path d="M7,3 C4.790861,3 3,4.790861 3,7 C3,9.209139 4.790861,11 7,11 C9.209139,11 11,9.209139 11,7 C10.9998594,4.79091925 9.20908075,3.00014061 7,3 Z M10,10 L13,13" id="Combined-Shape"></path>
</g>
</g>
</g>
</svg>

+ 46
- 0
assets/styles/_base.scss 查看文件

@@ -0,0 +1,46 @@
body {
margin: 0;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-anchor: none;
}

* {
box-sizing: border-box;
}

html {
min-height: 100%;
font-size: 16px;

@include media-below($bp-xxl) {
font-size: 14px;
}

@include media-below($bp-xs) {
font-size: 13px;
}

@include media-below($bp-xxs) {
font-size: 10.5px;
}
}

html,
body,
#root {
@include flex-column;
flex: 1 0 auto;
}

input[type='search']::-webkit-search-decoration,
input[type='search']::-webkit-search-cancel-button,
input[type='search']::-webkit-search-results-button,
input[type='search']::-webkit-search-results-decoration {
-webkit-appearance: none;
}

ul {
list-style: none;
padding: 0;
}

+ 7
- 0
assets/styles/_functions.scss 查看文件

@@ -0,0 +1,7 @@
@function pxToRem($target, $context: $base-font-size) {
@return ($target / $context) * 1rem;
}

@function pxToRemMd($target, $context: $base-font-size-md) {
@return ($target / $context) * 1rem;
}

+ 17
- 0
assets/styles/_layout.scss 查看文件

@@ -0,0 +1,17 @@
.l-page {
@include flex-column;
flex: 1 1 auto;
padding-bottom: 7rem;
position:relative;
@include media-below($bp-xl) {
padding-bottom: 4rem;
}
}

.l-section {
padding: 0 3.25rem;

@include media-below($bp-xl) {
padding: 0;
}
}

+ 81
- 0
assets/styles/_mixins.scss 查看文件

@@ -0,0 +1,81 @@
@mixin desktop {
@media (min-width: 1280px) {
@content;
}
}

@mixin desktop-lg {
@media (min-width: 1480px) {
@content;
}
}

@mixin tablet {
@media (max-width: 1024px) {
@content;
}
}

@mixin media-up($media) {
@media only screen and (min-width: $media) {
@content;
}
}

@mixin media-below($media) {
@media only screen and (max-width: #{$media - 0.02px}) {
@content;
}
}

@mixin media-between($mediaMin, $mediaMax) {
@media screen and (min-width: $mediaMin) and (max-width: #{$mediaMax - 0.02px}) {
@content;
}
}

@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}

@mixin flex-column {
display: flex;
flex-direction: column;
}

@mixin button-clear {
border: none;
padding: 0;
background-color: transparent;
}

@mixin outline-none {
&,
&:active,
&:focus {
outline: none;
}
}

@mixin reset-position {
position: relative;
top: initial;
left: initial;
right: initial;
bottom: initial;
}

@mixin text-ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

@mixin line-clamp($lines) {
display: -webkit-box;
-webkit-line-clamp: $lines;
-webkit-box-orient: vertical;
overflow: hidden;
}

+ 244
- 0
assets/styles/_overwrite.scss 查看文件

@@ -0,0 +1,244 @@
// Overwrite
.ais-ClearRefinements-button {
color: $grey-11;
font-size: pxToRem(14px);
letter-spacing: 0;
line-height: 1.15;
background-color: transparent;
border: none;
text-decoration: underline;
position: relative;
transition: all 0.2s;
outline: none;
cursor: pointer;

&[disabled] {
pointer-events: none;
opacity: 0.5;
cursor: auto;
}

&:hover {
color: $color-primary-light;
}

&:active {
color: $color-primary-dark;
}
}

.ais-RefinementList {
margin-bottom: pxToRem(32px);
margin-left: pxToRem(16px);

&.c-filter__refinement--closed {
display: none;
}
}

.ais-RefinementList.expanded {
.ais-RefinementList-showMore::before {
transform: rotate(180deg);
}
}

.ais-RefinementList-showMore {
color: $color-primary;
font-size: pxToRem(14px);
font-weight: 600;
letter-spacing: 0;
line-height: 1.56;
text-align: center;
background-color: transparent;
border: none;
position: relative;
margin-left: pxToRem(20px);
outline: none;
transition: all ease-in-out 0.3s;
cursor: pointer;

&[disabled] {
display: none;
}

&:hover {
color: $color-primary-light;
}

&:active {
color: $color-primary-dark;
}

&::before {
content: '';
background-image: url('../images/chevron-down.svg');
fill: $color-primary;
-webkit-text-stroke-color: $color-primary;
background-position: center;
width: pxToRem(20px);
height: pxToRem(20px);
position: absolute;
left: pxToRem(-22px);
transition: all 0.2s;
}
}

.ais-SearchBox {
display: flex;
justify-content: flex-end;
margin-bottom: pxToRem(24px);
}

.ais-SearchBox-input {
border: none;
color: $blue;
font-size: pxToRem(16px);
letter-spacing: 0;
line-height: 1.5;
outline: none;
-moz-appearance: none;
-webkit-appearance: none;
flex-grow: 1;

&::placeholder {
color: $blue;
font-size: pxToRem(16px);
}

@include media-below($bp-xl) {
font-size: pxToRemMd(16px);

&::placeholder {
font-size: pxToRemMd(16px);
}
}
}

.ais-SearchBox-form {
border: 1px solid $grey-6;
border-radius: $border-radius;
overflow: hidden;
padding: 0 pxToRem(12px);
height: pxToRem(33px);
align-items: center;
display: flex;
justify-content: space-between;
min-width: pxToRem(340px);
}

.ais-SearchBox-submit,
.ais-SearchBox-reset {
border: none;
background: transparent;
outline: none;
height: pxToRem(16px);

> svg {
color: $blue-1;
fill: $blue-1;
}
}

.ais-SearchBox-submitIcon {
width: pxToRem(14px);
height: pxToRem(14px);
color: $blue-1;
fill: $blue-1;
}

.ais-SearchBox-resetIcon {
width: pxToRem(14px);
height: pxToRem(14px);
}

.ais-SearchBox-reset {
margin-left: pxToRem(10px);
cursor: pointer;
}

.c-plaid-link {
padding: 0 !important;
background: transparent !important;
border-width: 0 !important;
border-radius: 0 !important;
box-shadow: $box-shadow !important;

&.c-plaid-link--select-card {
margin-top: pxToRem(40px);

.c-select-card__button {
margin-top: 0;
}
}
}

.ais-InfiniteHitsWrap {
min-height: pxToRem(200px);
}

.ais-Highlight-highlighted {
background: #fff1d6;
font-style: normal;
}

.acsb-trigger {
display: none !important;
visibility: hidden !important;
width: 0 !important;
height: 0 !important;
}

.ais-CurrentRefinements-list {
display: flex;
flex-wrap: wrap;

> :not(:last-child) {
margin-right: pxToRem(16px);
}
}

.ais-CurrentRefinements-item {
border-radius: $border-radius;
background-color: $dark-blue;
padding: pxToRem(4px) pxToRem(8px);
flex-shrink: 0;
margin-bottom: pxToRem(16px);
}

.ais-CurrentRefinements-item-link {
font-size: pxToRem(16px);
line-height: 1.5;
font-weight: 600;
color: $white;
display: flex;
align-items: center;
text-decoration: none;
}

.ais-CurrentRefinements-close {
color: $white;
width: pxToRem(24px);
margin-left: pxToRem(8px);
}

.recharts-surface {
overflow: visible;
}

.recharts-cartesian-axis-tick-value {
color: #9aa1a9;
font-size: 10px;
letter-spacing: 0;
line-height: 20px;
}
.recharts-tooltip-wrapper:empty{
display: 'none',
}
.recharts-text{
&.recharts-pie-label-text{
font-size: 14px;
@include media-below($bp-xl) {
font-size: 12px;
}
}
}

+ 127
- 0
assets/styles/_reset.scss 查看文件

@@ -0,0 +1,127 @@
/**
* Reset
*
*/

*,
*:before,
*:after {
box-sizing: border-box;
}

*,
body,
button,
input,
textarea,
select {
text-rendering: optimizeLegibility;
-moz-osx-font-smoothing: grayscale;
}

body,
div,
dl,
dt,
dd,
ul,
ol,
li,
h1,
h2,
h3,
h4,
h5,
h6,
pre,
form,
fieldset,
button,
input,
textarea,
p,
blockquote,
th,
td {
margin: 0;
padding: 0;
}

table {
border-collapse: collapse;
border-spacing: 0;
}

fieldset,
img {
border: 0;
}

address,
caption,
cite,
code,
dfn,
em,
th,
var {
font-style: normal;
font-weight: normal;
}

strong {
font-weight: 800;
}

ol,
ul {
list-style: none;
}

caption,
th {
text-align: left;
}

q:before,
q:after {
content: '';
}

abbr,
acronym {
border: 0;
}

svg {
flex-shrink: 0;
}

textarea,
input:matches([type='email'], [type='number'], [type='password'], [type='search'], [type='tel'], [type='text'], [type='url']) {
-webkit-appearance: none;

&::-webkit-autofill,
&::-webkit-contacts-auto-fill-button,
&::-webkit-credentials-auto-fill-button {
visibility: hidden;
user-select: none;
display: none !important;
position: absolute;
}
}

input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;

&::-webkit-autofill,
&::-webkit-contacts-auto-fill-button,
&::-webkit-credentials-auto-fill-button {
visibility: hidden;
user-select: none;
display: none !important;
position: absolute;
}
}

+ 57
- 0
assets/styles/_typography.scss 查看文件

@@ -0,0 +1,57 @@
body,
div,
dl,
dt,
dd,
ul,
ol,
li,
h1,
h2,
h3,
h4,
h5,
h6,
pre,
form,
fieldset,
button,
input,
textarea,
p,
blockquote,
th,
td {
font-family: $font-family;
}

p {
vertical-align: middle;
display: inline-block;
word-break: break-word;
font-size: pxToRem(16px);
line-height: 1.5;

@include media-below($bp-md) {
font-size: pxToRemMd(16px);
}
}

a {
font-size: inherit;
line-height: inherit;
color: inherit;
}

strong {
font-weight: bold;
}

h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 500;
}

+ 39
- 0
assets/styles/_utility.scss 查看文件

@@ -0,0 +1,39 @@
.u-mr-24 {
margin-right: 24px;
}

.u-ml-32 {
margin-left: pxToRem(32px);
}

.u-position-relative {
position: relative;
}

.u-column {
@include flex-column;
}

.u-display-none {
display: none;
}

.u-superscript {
font-size: pxToRem(14px);
font-weight: medium;
}

.u-text-align-right {
text-align: right;
}

.u-hide {
width: 0;
height: 0;
visibility: hidden;
display: none;
position: fixed;
top: -20px;
right: -20px;
z-index: -1;
}

+ 72
- 0
assets/styles/_variables.scss 查看文件

@@ -0,0 +1,72 @@
$base-font-size: 16px;
$base-font-size-md: 14px;

$font-family: 'Avenir Next';

// Colors
$color-primary: #024f86;
$color-primary-light: #024f86;
$color-primary-dark: #003246;
$yellow: #ffeac1;
$white: #ffffff;
$grey: #f4f4f4;
$grey-1: #ccced0;
$grey-2: #fafafa;
$grey-3: #c2c5c6;
$grey-4: #d8d8d8;
$grey-5: #808285;
$grey-6: #c9d6db;
$grey-7: rgba(128, 130, 133, 0.5);
$grey-8: rgba(201, 214, 219, 0.25);
$grey-9: #ebeff2;
$grey-10: #f0f5f6;
$grey-11: #8b8b8b;
$grey-12: #b0bfc540;
$grey-13: #9d9ea4;
$grey-14: #f7fafa;
$grey-15: #ebf2f2;
$dark-blue: #003246;
$blue: #4e7a8c;
$blue-1: #6e8fae;
$blue-2: #024f86;
$blue-3: #0f85ec;
$blue-4: #5c7e9f;
$blue-5: #dde5e7;
$black: #000;
$black-1: rgba(0, 0, 0, 0.3);
$black-2: rgba(32, 38, 43, 0.9);
$black-4: #172029;
$black-5: #272727;
$black-6: #1d2731;
$red: #ff5028;
$success: #09846b;
$success-1: #00876a;
$success-2: #008a68;

// Shadow
$button-shadow-hover: 0 5px 6px 0 rgba(112, 120, 135, 0.24);
$button-shadow-pressed: 0 2px 4px 0 rgba(112, 120, 135, 0.24);
$box-shadow: 0 3px 8px 0 rgba(112, 120, 135, 0.24);
$account-dropdown-shadow: 0 6px 38px 0 rgba(112, 120, 135, 0.56);

// Border Radius
$border-radius: 4px;
$border-radius-md: 8px;

// Breakpoints
$bp-xxs: 325px;
$bp-xs: 400px;
$bp-sm: 576px;
$bp-md: 768px;
$bp-lg: 992px;
$bp-xl: 1200px;
$bp-xxl: 1350px;

// z-index
$index-xxs: 1;
$index-xs: 2;
$index-sm: 3;
$index-md: 4;
$index-lg: 5;
$index-xl: 6;
$index-xxl: 7;

+ 60
- 0
assets/styles/components/_app-button.scss 查看文件

@@ -0,0 +1,60 @@
.c-button {
display: inline-flex;
align-items: center;
border-radius: $border-radius;
background-color: $color-primary;
box-shadow: 0 2px 4px 0 rgba(112, 120, 135, 0.24);
border: transparent;
padding: 8px 0;
color: $white;
width: 100%;
text-align: center;
justify-content: center;
font-family: "Avenir Next";
font-size: pxToRem(18px);
font-weight: 600;
letter-spacing: 0;
line-height: 26px;
outline: none;
text-transform: uppercase;
transition: all 0.3s ease-in-out;
cursor: pointer;

&.c-button--clean {
background: transparent;
border: 1px solid $color-primary;
color: $color-primary;

&:hover {
border-color: $color-primary-light;
color: $color-primary-light;
background-color: transparent;
}

&:active {
border-color: $color-primary-dark;
color: $color-primary-dark;
}
}

&.c-button--dropdown {
justify-content: flex-end;
padding: 8px 14px;
background-image: url("../../images/down.svg");
background-repeat: no-repeat;
background-position: 8% 50%;
}

&[disabled] {
pointer-events: none;
opacity: 0.5;
}

&:hover {
background-color: $color-primary-light;
}

&:active {
background-color: $color-primary-dark;
}
}

+ 45
- 0
assets/styles/components/_auth-card.scss 查看文件

@@ -0,0 +1,45 @@
.c-auth-card {
max-width: pxToRem(624px);
width: 100%;
box-shadow: $box-shadow;
border-radius: $border-radius;
border: 1px solid $color-primary-light;
padding: pxToRem(24px) pxToRem(40px) pxToRem(32px);

@include media-below($bp-md) {
border: none;
box-shadow: none;
padding: 0;
max-width: 100%;

.c-auth-card__title {
text-align: left;
font-size: pxToRemMd(36px);
margin-bottom: pxToRemMd(6px);
}

.c-auth-card__subtitle {
font-size: pxToRemMd(16px);
text-align: left;
}
}
}

.c-auth-card__title {
text-align: left;
font-size: pxToRem(36px);
line-height: 1.22;
color: $dark-blue;
font-weight: 400;
margin-bottom: pxToRem(16px);
}

.c-auth-card__subtitle {
font-size: pxToRem(16px);
line-height: 1.5;
letter-spacing: 0;
color: $color-primary;
text-align: left;
width: 100%;
font-weight: 600;
}

+ 23
- 0
assets/styles/components/_auth.scss 查看文件

@@ -0,0 +1,23 @@
.c-auth {
@include flex-center;
flex-direction: column;
padding-bottom: pxToRem(56px);

@include media-below($bp-md) {
padding: 0 pxToRemMd(24px) pxToRemMd(92px);

.c-auth__title {
margin: pxToRemMd(48px) auto;
font-size: pxToRemMd(24px);
line-height: 1.35;
}
}
}

.c-auth__title {
margin: pxToRem(56px) auto pxToRem(80px);
font-size: pxToRem(36px);
line-height: 1.22;
color: $dark-blue;
font-weight: bold;
}

+ 173
- 0
assets/styles/components/_button.scss 查看文件

@@ -0,0 +1,173 @@
.c-btn {
@include outline-none;
@include button-clear;
@include flex-center;
font-size: pxToRem(18px);
line-height: 1.35;
padding: pxToRem(8px) pxToRem(8px);
border-radius: $border-radius;
box-shadow: $button-shadow-pressed;
color: inherit;
font-weight: 600;
letter-spacing: 0;
text-align: center;
text-transform: uppercase;
user-select: none;
white-space: nowrap;
min-width: pxToRem(120px);
flex-shrink: 0;
cursor: pointer;
transition: background-color 0.2s, color 0.2s;

&:disabled {
opacity: 0.5;
cursor: auto;
}

&.c-btn--primary {
background-color: $color-primary;
color: $white;
border: 1px solid $color-primary;

&:disabled {
&:hover {
background-color: $color-primary;
box-shadow: none;
}
}

&:hover {
background-color: $color-primary-light;
box-shadow: $button-shadow-hover;
}

&:focus,
&:active {
background-color: $color-primary-dark;
box-shadow: $button-shadow-pressed;
}
}

&.c-btn--primary-outlined {
background-color: transparent;
color: $color-primary;
border: 1px solid $color-primary;

&:disabled {
&:hover {
color: $color-primary;
border: 1px solid $color-primary;
}
}

&:hover {
color: $color-primary;
border: 1px solid $color-primary;
}

&:focus,
&:active {
color: $color-primary;
border: 1px solid $color-primary;
}
}

&.c-btn--blue {
background-color: $blue-3;
color: $white;
background-color: $blue-3;
}

&.c-btn--white {
background-color: $white;
color: $grey-3;
border: 1px solid $grey-4;
box-shadow: $box-shadow;

&:disabled {
&:hover {
background-color: $white;
color: $grey-3;
}
}

&:hover {
color: $grey-5;
}

&:focus,
&:active {
background-color: $grey;
}
}

&.c-btn--primary-clear {
background-color: transparent;
color: $color-primary;
border: none;
box-shadow: none;
padding: 0;
}

&.c-btn--auto {
min-width: auto;
}

&.c-btn--sm {
font-size: pxToRem(16px);
line-height: 1.5;
padding: pxToRem(4px) pxToRem(15px);
}

&.c-btn--capitalize {
text-transform: capitalize;
}

&.c-btn--bank-acount-card {
padding: 0 pxToRem(16px);
min-height: pxToRem(32px);
min-width: pxToRem(120px);
font-size: pxToRem(16px);
line-height: 1.5;
}

&.c-btn--hidden {
visibility: hidden;
height: 0;
}

@include media-below($bp-md) {
padding: pxToRemMd(4px) pxToRemMd(25px);
font-size: pxToRemMd(16px);
line-height: 1.5;
min-width: pxToRemMd(80px);

&.c-btn--auth {
padding: pxToRemMd(12px) pxToRemMd(25px);
line-height: 1.35;
font-size: pxToRemMd(18px);
}

&.c-btn--sm {
padding: pxToRemMd(4px) pxToRemMd(15px);
}

&.c-btn--bank-acount-card {
flex-grow: 1;
min-height: pxToRemMd(40px);
padding: pxToRemMd(8px) pxToRemMd(16px);
font-size: pxToRemMd(18px);
line-height: 1.33;
}

&.c-btn--lg {
padding: pxToRemMd(7.5px) pxToRemMd(15px);
font-size: pxToRemMd(18px);
line-height: 1.5;
}
}

@include media-below($bp-xs) {
white-space: unset;
}
}

+ 46
- 0
assets/styles/components/_error-page.scss 查看文件

@@ -0,0 +1,46 @@
.c-error-page {
margin-top: pxToRem(120px);

@include media-below($bp-md) {
margin-top: pxToRemMd(120px);

.c-error-page__title {
font-size: pxToRemMd(160px);
margin-bottom: pxToRemMd(27px);
}

.c-error-page__text {
margin-bottom: pxToRem(24px);
}
}
}

.c-error-page__content-container {
@include flex-center;
}

.c-error-page__content {
@include flex-column;
align-items: center;
padding: 0 pxToRem(32px);
}

.c-error-page__title {
font-size: pxToRem(160px);
line-height: 1.35;
color: $dark-blue;
margin-bottom: pxToRem(32px);
color: $color-primary;
font-weight: bold;
}

.c-error-page__text {
font-weight: 600;
margin-bottom: pxToRem(24px);
text-align: center;
}

.c-error-page__button {
margin-bottom: pxToRem(16px);
min-width: pxToRem(250px);
}

+ 23
- 0
assets/styles/components/_forgot-password.scss 查看文件

@@ -0,0 +1,23 @@
.c-reset-security {
padding-top: pxToRem(56px);

@include media-below($bp-md) {
padding-top: pxToRemMd(40px);

.c-reset-security__button {
width: 100%;
margin-top: pxToRemMd(44px);
}
}
}

.c-reset-security__question {
color: $dark-blue;
font-weight: 600;
margin-bottom: pxToRem(20px);
}

.c-reset-security__button {
width: 100%;
margin-top: pxToRem(48px);
}

+ 7
- 0
assets/styles/components/_icon-button.scss 查看文件

@@ -0,0 +1,7 @@
.c-icon-button {
@include flex-center;
@include outline-none;
@include button-clear;
user-select: none;
cursor: pointer;
}

+ 479
- 0
assets/styles/components/_input.scss 查看文件

@@ -0,0 +1,479 @@
.c-input {
@include flex-column;
position: relative;

&.c-input--error {
.c-input__field,
.c-select__control,
.c-select__control:hover {
border-color: $red;
}
}

&.c-input--password {
.c-input__icon {
position: absolute;
right: 0;
top: 50%;
transform: translate(0, -50%);
margin-right: pxToRem(12px);
width: pxToRem(24px);
height: pxToRem(24px);
display: flex;

svg {
width: pxToRem(24px);
height: pxToRem(24px);
color: $black;
}
}

.c-input__caps-lock {
position: absolute;
right: 0;
top: 50%;
transform: translate(0, -50%);
margin-right: pxToRem(40px);
width: pxToRem(24px);
height: pxToRem(24px);
display: flex;
width: pxToRem(24px);
height: pxToRem(24px);
color: $black;
}

.c-input__field {
padding-right: pxToRem(72px);
}
}

&.c-input--demi-bold {
.c-input__field {
font-weight: 600;
}
}

&.c-input--search {
position: relative;
width: 100%;

.c-input__icon {
width: pxToRem(24px);
height: pxToRem(24px);
position: absolute;
right: 0;
top: 50%;
transform: translate(0, -50%);
color: $blue-1;
margin-right: pxToRem(12px);
}

&.c-input--search-management {
max-width: pxToRem(344px);
margin-right: pxToRem(24px);

.c-input__field {
height: pxToRem(34px);
font-size: pxToRem(16px);
line-height: 1.5;
letter-spacing: 0;
}
}
}

&.c-input--center-text {
input {
text-align: center;
}
}

@include media-below($bp-xl) {
&.c-input--search {
&.c-input--search-management {
max-width: 100%;
margin-right: pxToRemMd(16px);

.c-input__field {
height: pxToRemMd(32px);
font-size: pxToRemMd(16px);
line-height: 1.5;
letter-spacing: 0;
}
}
}

.c-input__label {
font-size: pxToRemMd(16px);
}

.c-input__field {
font-size: pxToRemMd(16px);
}

.c-input__error {
font-size: pxToRemMd(14px);
}

.c-select__control {
&.c-select__control {
font-size: pxToRemMd(16px);
min-height: 0;

.c-select__input,
.c-select__placeholder {
font-size: pxToRemMd(16px);
}

.c-select__indicator {
> svg {
width: pxToRemMd(16px);
height: pxToRemMd(16px);
}
}
}
}

.c-select__menu {
.c-select__option,
.c-select__menu-notice {
font-size: pxToRemMd(16px);
}
}

.c-input__link {
a,
span {
font-size: pxToRemMd(16px);
}
}

//Overide
.c-password-strength__container {
font-size: pxToRemMd(16px);
}

.c-phone-number {
.PhoneInput {
font-size: pxToRemMd(16px);

&::placeholder {
font-size: pxToRemMd(16px);
}
}

.PhoneInputInput {
font-size: pxToRemMd(16px);
}
}
}

&.c-input--dropdown-full-height {
.c-select__menu {
max-height: initial;
}
}
}

.c-input__label {
color: $blue;
font-size: pxToRem(16px);
font-weight: 600;
letter-spacing: 0;
line-height: 1.75;
margin-bottom: pxToRem(4px);
}

.c-input__field-wrap {
width: 100%;
position: relative;
}

.c-input__field {
@include outline-none;
border: 1px solid $grey-6;
border-radius: $border-radius;
font-size: pxToRem(16px);
line-height: 1.75;
height: pxToRem(50px);
padding: 0 pxToRem(12px);
color: $blue;
background-color: $white;
width: 100%;

&:disabled {
background-color: $grey-8;
border-color: $grey-6;
}

&:focus {
border-color: $color-primary;
}
}

.c-input__error {
position: absolute;
top: 100%;
left: 0;
right: 0;
color: $red;
font-size: pxToRem(14px);
line-height: 1.35;
font-weight: 500;
margin: pxToRem(4px) 0;
}

.c-select__control {
&.c-select__control {
@include outline-none;
border: 1px solid $grey-6;
border-radius: $border-radius;
font-size: pxToRem(16px);
line-height: 1.75;
height: pxToRem(50px);
padding: 0 pxToRem(12px);
color: $blue;
background-color: $white;
box-shadow: none;

&:hover {
border-color: $grey-6;
}

&.c-select__control--is-focused {
border-color: $color-primary;
box-shadow: none;

&:hover {
border-color: $color-primary;
}
&.c-select__control--menu-is-open{
.c-select__indicator {
svg {
transform: rotate(-180deg);
}
}
}
}

.css-1uccc91-singleValue {
color: $blue;
margin: 0;
}

.css-b8ldur-Input {
margin: 0;
}

.c-select__value-container {
height: 100%;
padding: 0;
padding-right: pxToRem(32px);
}

.c-select__input,
.c-select__placeholder {
font-size: pxToRem(16px);
line-height: 1.75;
letter-spacing: 0;
color: $blue;
}

.c-select__indicator-separator {
display: none;
}

.c-select__indicator {
padding: 0;

> svg {
width: pxToRem(16px);
height: pxToRem(16px);
color: $blue;
transform: rotate(0);
transition: transform 0.2s;
}
}

&.c-select__control--is-disabled {
background-color: $grey-8;
}
}
}

.c-select__menu {
@include flex-column;
position: absolute;
top: 100%;
left: 0;
right: 0;
margin-top: pxToRem(4px);
margin-bottom: pxToRem(4px);
border: 1px solid $grey-6;
border-radius: $border-radius;
box-shadow: $box-shadow;
max-height: pxToRem(150px);
overflow: auto;

.c-select__menu-list {
@include flex-column;
padding: 0;
flex-grow: 1;
}

.c-select__option,
.c-select__menu-notice {
padding: pxToRem(12px) pxToRem(15px);
font-size: pxToRem(16px);
line-height: 1.75;
letter-spacing: 0;
color: $blue;
text-align: left;

&:hover {
background-color: $grey-2;
}

&.c-select__option--is-selected {
background-color: $grey-2;
}

&.c-select__option--is-focused {
background-color: $grey-2;
}
}
}

.c-input__link {
position: absolute;
top: 0;
right: 0;

a,
span {
color: $grey-11;
font-size: pxToRem(16px);
letter-spacing: 0;
line-height: 1.15;
text-decoration: underline;
cursor: pointer;
}
}

//Overide
.c-password-strength__container {
margin-top: pxToRem(8px);
font-size: pxToRem(16px);

& .c-password-strength__line--wrapper {
border-radius: 8px;
overflow: hidden;
background-color: $grey;
height: pxToRem(5px);

.c-password-strength__line {
height: pxToRem(5px);
left: 0;
top: 0;
}
}
}

.c-password {
min-height: pxToRem(110px);

@include media-below($bp-xl) {
min-height: pxToRemMd(110px);
}
}

.c-phone-number {
.PhoneInput {
@include outline-none;
box-sizing: border-box;
border: 1px solid $grey-6;
border-radius: $border-radius;
font-size: pxToRem(16px);
line-height: 1.75;
min-height: pxToRem(50px);
color: $blue;
background-color: $white;
box-shadow: none;
width: 100%;
overflow: hidden;

&::placeholder {
font-size: pxToRem(16px);
line-height: 1.75;
}

&:disabled {
background-color: $grey-8;
border-color: $grey-6;
}

&.PhoneInput--focus {
border-color: $color-primary;

.PhoneInputCountry {
border-color: $color-primary;
}
}
}

.PhoneInputCountry {
@include flex-center;
width: pxToRem(96px);
border-right: 1px solid $grey-6;
}

.PhoneInputCountryIcon {
margin-right: pxToRem(16px);
width: auto;
height: auto;
border: none;
}

.PhoneInputCountryIconImg {
width: pxToRem(36px);
object-fit: contain;
}

.PhoneInputCountrySelectArrow {
border: none;
width: 0;
height: 0;
transform: translate(0);
border-left: pxToRem(8px) solid transparent;
border-right: pxToRem(8px) solid transparent;
border-top: pxToRem(8px) solid $blue;
}

.PhoneInputInput {
@include outline-none;
border-color: transparent;
height: 100%;
font-size: pxToRem(16px);
line-height: 1.75;
padding: 0;
color: $blue;
background-color: $white;
width: 100%;
margin: 0;
padding: 0 pxToRem(26px);
height: pxToRem(50px);
}

.PhoneInputCountry {
margin-right: 0;
}

&.c-input--error {
.PhoneInput {
border-color: $red;

.PhoneInputCountry {
border-color: $red;
}
}
}
}

+ 72
- 0
assets/styles/components/_loader.scss 查看文件

@@ -0,0 +1,72 @@
.c-loader__wrapper {
@include flex-column;
flex: 1 1 auto;
position: relative;
min-height: 0;
min-width: 0;

&.c-loader__wrapper--block {
box-shadow: $box-shadow;

.c-loader {
position: relative;
top: unset;
left: unset;
right: unset;
bottom: unset;
}
}

&.c-loader__wrapper--full-height {
height: 100%;
}

&.c-loader__wrapper--no-shadow {
box-shadow: none;
}

.c-loader {
@include flex-center;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: pxToRem(15px) 0;
background-color: rgba(255, 255, 255, 0.4);
z-index: $index-lg;

&.c-loader--page {
position: fixed;

.c-loader__icon {
border: 20px solid transparent;
width: pxToRem(200px);
height: pxToRem(200px);
border-bottom-color: $color-primary;
border-top-color: $color-primary;
}
}
}

.c-loader__icon {
border-radius: 50%;
border: 10px solid transparent;
border-bottom-color: $color-primary;
border-top-color: $color-primary;
animation: 1s loader-animation linear infinite;
width: pxToRem(100px);
height: pxToRem(100px);
}

@keyframes loader-animation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
}

+ 31
- 0
assets/styles/components/_login-card.scss 查看文件

@@ -0,0 +1,31 @@
.c-login-card {
border: 1px solid $color-primary-light;
border-radius: $border-radius;
box-shadow: $box-shadow;
max-width: pxToRem(624px);
width: 100%;
margin: pxToRem(28px) auto 0;
padding: pxToRem(36px) pxToRem(40px) pxToRem(32px);
}

.c-login-card__note {
color: $color-primary;
font-weight: 600;
margin-bottom: pxToRem(37px);
}

.c-login-card__form {
display: grid;
grid-row-gap: pxToRem(24px);
}

.c-login-card__submit {
margin-top: pxToRem(24px);
width: 100%;
}

.c-login-card__question {
color: $blue;
font-weight: 600;
margin-bottom: pxToRem(16px);
}

+ 72
- 0
assets/styles/components/_login.scss 查看文件

@@ -0,0 +1,72 @@
.c-login {
&.c-login--user {
.c-login__form {
.c-input:first-child {
margin-bottom: pxToRem(20px);
}
}
}

@include media-below($bp-xl) {
.c-login__link {
margin-top: pxToRemMd(70px);
}
}

@include media-below($bp-md) {
.c-login__form {
margin: pxToRemMd(36px) 0 0;
}

.c-login__button {
margin-bottom: pxToRemMd(40px);
margin-top: pxToRemMd(36px);
}

.c-login__link {
margin-top: pxToRemMd(80px);
}

&.c-login--user {
.c-login__form {
.c-input:first-child {
margin-bottom: pxToRemMd(20px);
}
}
}
}
}

.c-login__link {
color: $color-primary;
font-weight: 600;
margin-top: pxToRem(40px);
width: max-content;
}

.c-login__form {
margin: pxToRem(36px) 0 0;
> form {
@include flex-column;
}
}

.c-login__button {
width: 100%;
margin-top: pxToRem(68px);
margin-bottom: pxToRem(24px);
}

.c-login__text {
text-align: center;
width: 100%;
color: $blue;

a {
color: $color-primary;
font-weight: bold;
letter-spacing: inherit;
font-size: inherit;
line-height: inherit;
}
}

+ 169
- 0
assets/styles/components/_modal.scss 查看文件

@@ -0,0 +1,169 @@
$header-height-desktop: pxToRem(80px);
$header-height-mobile: pxToRemMd(74px);

.c-modal-wrap {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: $index-xl;
background-color: $black-1;

&.c-modal-wrap--no-bg {
background-color: transparent;
}

&.c-modal-wrap--over-modal {
background-color: transparent;
z-index: $index-xxl;
}

&.c-modal-wrap--sm {
.c-modal {
max-width: pxToRem(390px);
width: 100%;
}

.c-modal__header {
padding: pxToRem(12px);
}
}

.c-modal__header {
padding: pxToRem(12px);
}

&.c-modal-wrap--md {
.c-modal {
max-width: pxToRem(521px);
width: 100%;
}

.c-modal__header {
padding: pxToRem(12px) pxToRem(20px);
}
}

&.c-modal-wrap--lg {
.c-modal {
max-width: pxToRem(782px);
width: 100%;
}

.c-modal__header {
padding: pxToRem(12px) pxToRem(20px);
}
}

&.c-modal-wrap--close {
display: none;
}

@include media-below($bp-xl) {
&,
&.c-modal-wrap--sm,
&.c-modal-wrap--md {
.c-modal {
margin: $header-height-mobile auto $header-height-mobile;
max-height: calc(100vh - #{2 * $header-height-mobile});
}
}
}

@include media-below($bp-md) {
&,
&.c-modal-wrap--sm,
&.c-modal-wrap--md {
.c-modal__header {
padding: pxToRemMd(16px);
}

.c-modal__title {
font-size: pxToRemMd(16px);
line-height: 1.4;
}

.c-modal__close {
width: pxToRemMd(24px);
height: pxToRemMd(24px);
}

.c-modal__back {
width: pxToRemMd(24px);
height: pxToRemMd(24px);
margin-right: pxToRemMd(8px);
}

.c-modal__back-button {
margin-left: -#{pxToRemMd(8px)};
}

.c-modal,
&.c-modal-wrap--lg .c-modal {
max-width: 100%;
max-height: 100vh;
height: 100%;
margin: 0;
border-radius: 0;
}

&.c-modal-wrap--mobile-modal {
display: flex;
padding: 0 pxToRemMd(20px);

.c-modal {
height: auto;
margin: auto;
border-radius: 2px;
}
}
}
}
}

.c-modal {
@include flex-column;
box-shadow: $box-shadow;
border-radius: $border-radius;
background-color: $white;
margin: $header-height-desktop auto $header-height-desktop;
max-height: calc(100vh - #{2 * $header-height-desktop});
position: relative;
}

.c-modal__header {
display: flex;
align-items: center;
box-shadow: $box-shadow;
z-index: $index-xxs;
}

.c-modal__title {
@include text-ellipsis;
font-size: pxToRem(16px);
font-weight: 600;
line-height: 1.5;
color: $dark-blue;
padding-right: pxToRem(10px);
margin-right: auto;
}

.c-modal__close {
width: pxToRem(16px);
height: pxToRem(16px);
color: $dark-blue;
}

.c-modal__back {
width: pxToRem(16px);
height: pxToRem(16px);
color: $dark-blue;
margin-right: pxToRem(10px);
}

.c-modal__body {
@include flex-column;
flex: 1 1 auto;
overflow: auto;
}

+ 29
- 0
assets/styles/components/_radio.scss 查看文件

@@ -0,0 +1,29 @@
.c-radio {
display: flex;
cursor: pointer;

&.c-radio--selected {
border-color: $dark-blue;
}
}

.c-radio__field {
display: none;
}

.c-radio__indicator {
margin-top: pxToRem(4px);
margin-right: pxToRem(16px);
}

.c-radio__icon {
width: pxToRem(16px);
height: pxToRem(16px);
}

.c-radio__text {
font-size: pxToRem(14px);
line-height: 1.15;
color: $blue;
user-select: none;
}

+ 55
- 0
components/loader/route-loader/CircularIndeterminate.jsx 查看文件

@@ -0,0 +1,55 @@
import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';

const CircularIndeterminate = () => {
const router = useRouter();

const [loading, setLoading] = useState(false);

useEffect(() => {
const handleStart = (url) => url !== router.asPath && setLoading(true);
const handleComplete = (url) => url === router.asPath && setLoading(false);

router.events.on('routeChangeStart', handleStart);
router.events.on('routeChangeComplete', handleComplete);
router.events.on('routeChangeError', handleComplete);

return () => {
router.events.off('routeChangeStart', handleStart);
router.events.off('routeChangeComplete', handleComplete);
router.events.off('routeChangeError', handleComplete);
};
});
return (
loading && (
<Box
sx={{
display: 'flex',
zIndex: 99,
height: '100vh',
width: '100vw',
justifyContent: 'center',
alignItems: 'center',
position: 'fixed',
top: 0,
left: 0,
}}
>
<Box
sx={{
position: 'absolute',
top: '48%',
left: '48%',
marginX: 'auto',
}}
>
<CircularProgress color="inherit" size={60} thickness={4} />
</Box>
</Box>
)
);
};

export default CircularIndeterminate;

+ 5
- 0
components/loader/route-loader/CircularIndeterminate.mock.js 查看文件

@@ -0,0 +1,5 @@
const base = {};

export const mockCircularIndeterminateProps = {
base,
};

+ 20
- 0
components/loader/route-loader/CircularIndeterminate.stories.jsx 查看文件

@@ -0,0 +1,20 @@
import CircularIndeterminate from './CircularIndeterminate';
import { mockCircularIndeterminateProps } from './CircularIndeterminate.mock';

const obj = {
title: 'laoader/CircularIndeterminate',
component: CircularIndeterminate,
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
argTypes: {},
}; //eslint-disable-line

export default obj;
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template = (args) => <CircularIndeterminate {...args} />;

export const Base = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args

Base.args = {
...mockCircularIndeterminateProps.base,
};

+ 93
- 0
components/sass-components/Button/Button.js 查看文件

@@ -0,0 +1,93 @@
import React, { useRef } from 'react';
import PropType from 'prop-types';

const Button = ({
variant,
size,
children,
authButton,
type,
onClick,
textTransform,
className,
disabled,
hidden,
minWidth,
...restProps
}) => {
const buttonRef = useRef(null);

function styles() {
let style = 'c-btn';

if (variant) {
style += ` c-btn--${variant}`;
}

if (size) {
style += ` c-btn--${size}`;
}

if (textTransform) {
style += ` c-btn--${textTransform}`;
}

if (authButton) {
style += ` c-btn--auth`;
}

if (minWidth) {
style += ` c-btn--${minWidth}`;
}

if (hidden) {
style += ` c-btn--hidden`;
}

if (className) {
style += ` ${className}`;
}

return style;
}

function handleClick() {
buttonRef.current.blur();
if (typeof onClick === 'function') {
onClick();
}
}

return (
<button
ref={buttonRef}
className={styles()}
onClick={handleClick}
type={type}
disabled={disabled}
{...restProps}
>
{children}
</button>
);
};

Button.propTypes = {
children: PropType.node,
textTransform: PropType.oneOf(['uppercase', 'capitalize']),
size: PropType.oneOf(['sm', 'md', 'lg', 'xl']),
authButton: PropType.bool,
variant: PropType.string,
type: PropType.oneOf(['button', 'submit', 'reset']),
onClick: PropType.func,
className: PropType.string,
disabled: PropType.bool,
minWidth: PropType.oneOf(['auto']),
hidden: PropType.bool,
};

Button.defaultProps = {
type: 'button',
};

export default Button;

+ 32
- 0
components/sass-components/IconButton/IconButton.js 查看文件

@@ -0,0 +1,32 @@
import React, { useRef } from 'react';
import PropType from 'prop-types';

const IconButton = ({ children, onClick, className }) => {
const buttonRef = useRef(null);

function handleClick() {
buttonRef.current.blur();
if (typeof onClick === 'function') {
onClick();
}
}

return (
<button
type="button"
ref={buttonRef}
onClick={handleClick}
className={`c-icon-button ${className && className}`}
>
{children}
</button>
);
};

IconButton.propTypes = {
children: PropType.node,
onClick: PropType.func,
className: PropType.string,
};

export default IconButton;

+ 187
- 0
components/sass-components/InputFields/BaseInputField.js 查看文件

@@ -0,0 +1,187 @@
import { ErrorMessage } from 'formik';
import PropTypes from 'prop-types';
import React, { useEffect, useRef, useState } from 'react';
import { ReactComponent as CapsLock } from '../../../assets/images/svg/caps-lock.svg';
import { ReactComponent as EyeOff } from '../../../assets/images/svg/eye-off.svg';
import { ReactComponent as EyeOn } from '../../../assets/images/svg/eye-on.svg';
import { ReactComponent as Search } from '../../../assets/images/svg/search.svg';
import IconButton from '../IconButton/IconButton';

const BaseInputField = ({
type,
label,
field,
form,
placeholder,
clearPlaceholderOnFocus = true,
isSearch,
className,
disabled,
centerText,
link,
errorMessage,
autoFocus,
isCapsLockOn,
...props
}) => {
const [inputPlaceholder, setPlaceholder] = useState(placeholder);

const inputField = useRef(null);

useEffect(() => {
if (autoFocus) {
inputField.current.focus();
}
}, [autoFocus, inputField]);

useEffect(() => {
if (errorMessage) {
form.setFieldError(field.name, errorMessage);
}
}, [errorMessage]); // eslint-disable-line

useEffect(() => {
setPlaceholder(placeholder);
}, [placeholder]);

const [inputType, setInputType] = useState('password');
const passwordInput = type === 'password' ? ' c-input--password' : '';

const showPassword = () => {
if (inputType === 'password') {
setInputType('text');
} else {
setInputType('password');
}
};

// Nester Formik Field Names get bugged because of Undefined values, so i had to fix it like this
// If you ask why 0 and 1? I dont see a need for forms to be nested more then 2 levels?
const fieldName = field.name.split('.');

const formError =
fieldName[0] && fieldName[1]
? form.errors[fieldName[0]] && form.errors[fieldName[0]][fieldName[1]]
: form.errors[fieldName[0]];

const formTouched =
fieldName[0] && fieldName[1]
? form.touched[fieldName[0]] && form.touched[fieldName[0]][fieldName[1]]
: form.touched[fieldName[0]];

function styles() {
let style = 'c-input';

if (formError && formTouched) {
style += ` c-input--error`;
}

if (type === 'password') {
style += ` c-input--password`;
}

if (isSearch) {
style += ` c-input--search`;
}

if (centerText) {
style += ` c-input--center-text`;
}

if (type === 'number') {
style += ` c-input--demi-bold`;
}

if (className) {
style += ` ${className}`;
}

return style;
}

const additionalActions = () => {
if (!clearPlaceholderOnFocus) {
return null;
}

return {
onFocus: () => {
setPlaceholder('');
},
onBlur: (e) => {
setPlaceholder(placeholder);
field.onBlur(e);
},
};
};
return (
<div className={styles()}>
{!!label && (
<label className="c-input__label" htmlFor={field.name}>
{label}
</label>
)}
{link && <div className="c-input__link">{link}</div>}
<div className="c-input__field-wrap">
<input
ref={inputField}
type={type === 'password' ? inputType : type}
placeholder={inputPlaceholder}
disabled={disabled}
{...field}
{...props}
{...additionalActions()}
className="c-input__field"
/>
{!!isSearch && <Search className="c-input__icon" />}
{!!passwordInput && (
<>
{isCapsLockOn && <CapsLock className="c-input__caps-lock" />}
<IconButton
onClick={() => {
showPassword();
}}
className="c-input__icon"
>
{inputType === 'password' ? <EyeOff /> : <EyeOn />}
</IconButton>
</>
)}
</div>

<ErrorMessage name={field.name}>
{(errorMessage) => (
<span className="c-input__error">{errorMessage}</span>
)}
</ErrorMessage>
</div>
);
};
BaseInputField.propTypes = {
type: PropTypes.string,
field: PropTypes.shape({
name: PropTypes.string,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
}),
form: PropTypes.shape({
errors: PropTypes.shape({}),
setFieldError: PropTypes.func,
touched: PropTypes.shape({}),
}),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]),
placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
disabled: PropTypes.bool,
isSearch: PropTypes.bool,
className: PropTypes.string,
link: PropTypes.node,
errorMessage: PropTypes.string,
centerText: PropTypes.bool,
clearPlaceholderOnFocus: PropTypes.bool,
demiBold: PropTypes.bool,
touched: PropTypes.bool,
autoFocus: PropTypes.bool,
isCapsLockOn: PropTypes.bool,
};

export default BaseInputField;

+ 40
- 0
components/sass-components/InputFields/Checkbox.js 查看文件

@@ -0,0 +1,40 @@
import PropTypes from 'prop-types';
import React from 'react';
import { ReactComponent as Checked } from '../../../assets/images/svg/checked.svg';
import { ReactComponent as Unchecked } from '../../../assets/images/svg/unchecked.svg';

const Checkbox = ({ className, children, name, onChange, checked, field }) => (
<label htmlFor={name} className={`c-checkbox ${className || ''}`}>
<input
name={name}
id={name}
className="c-checkbox__field"
type="checkbox"
checked={checked}
{...field}
onChange={onChange || field.onChange}
/>

<div className="c-checkbox__indicator">
{checked ? (
<Checked className="c-checkbox__icon" />
) : (
<Unchecked className="c-checkbox__icon" />
)}
</div>
<div className="c-checkbox__text">{children}</div>
</label>
);

Checkbox.propTypes = {
children: PropTypes.node,
onChange: PropTypes.func,
checked: PropTypes.bool,
name: PropTypes.string,
field: PropTypes.shape({
onChange: PropTypes.func,
}),
className: PropTypes.string,
};

export default Checkbox;

+ 123
- 0
components/sass-components/InputFields/CurrencyField.js 查看文件

@@ -0,0 +1,123 @@
import { ErrorMessage, useField } from 'formik';
import PropTypes from 'prop-types';
import React, { useEffect, useRef } from 'react';
import CurrencyInput from 'react-currency-input-field';
import { formatMoneyNumeral } from '../../../util/helpers/numeralHelpers';
import {
K_KEYCODE,
MINUS_SYMBOL,
NUMPAD_MINUS_SYMBOL,
NUMPAD_PLUS_SYMBOL,
PLUS_SYMBOL,
} from '../../constants/keyCodeConstants';

const CurrencyField = ({
autoFocus,
notCentered,
notBold,
label,
onChange,
value,
...props
}) => {
const [field, meta] = useField(props);
const inputField = useRef(null);
function styles() {
let style = 'c-currency-field';

if (meta.error && meta.touched) {
style += ` c-currency-field--error`;
}

if (notCentered) {
style += ` c-currency-field--not-centered`;
}

if (notBold) {
style += ` c-currency-field--not-bold`;
}

return style;
}

useEffect(() => {
if (autoFocus) {
inputField.current.focus();
}
}, [autoFocus, inputField]);

const onKeydownHandler = (event) => {
if (
event.keyCode === MINUS_SYMBOL ||
event.keyCode === PLUS_SYMBOL ||
event.keyCode === NUMPAD_MINUS_SYMBOL ||
event.keyCode === NUMPAD_PLUS_SYMBOL ||
event.keyCode === K_KEYCODE
) {
event.preventDefault();
}
};

const prefix = formatMoneyNumeral(0);
const prefixSymbol = () => {
if (prefix.includes('CAD')) {
return 'CAD ';
}

return '$';
};

return (
<div className={styles()}>
{!!label && (
<label className="c-currency-field__label" htmlFor={field.name}>
{label}
</label>
)}
{value ? (
<CurrencyInput
{...props}
prefix={prefixSymbol()}
onValueChange={(value) => {
onChange(value ? Number(value) : '');
}}
onKeyDown={(event) => onKeydownHandler(event)}
ref={inputField}
defaultValue={0}
value={value}
/>
) : (
<CurrencyInput
{...props}
prefix={prefixSymbol()}
onValueChange={(value) => {
onChange(value ? Number(value) : '');
}}
onKeyDown={(event) => onKeydownHandler(event)}
ref={inputField}
/>
)}
<ErrorMessage name={field.name}>
{(errorMessage) => (
<span className="c-currency-field__error">{errorMessage}</span>
)}
</ErrorMessage>
</div>
);
};

CurrencyField.propTypes = {
field: PropTypes.shape({
name: PropTypes.string,
}),
form: PropTypes.shape({}),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]),
disabled: PropTypes.bool,
onChange: PropTypes.func,
autoFocus: PropTypes.bool,
notCentered: PropTypes.bool,
notBold: PropTypes.bool,
value: PropTypes.number,
};

export default CurrencyField;

+ 33
- 0
components/sass-components/InputFields/EmailField.js 查看文件

@@ -0,0 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';

import BaseInputField from './BaseInputField';

const EmailField = ({
field,
form,
label,
placeholder,
disabled,
...props
}) => (
<BaseInputField
type="email"
label={label}
placeholder={placeholder}
disabled={disabled}
form={form}
field={field}
{...props}
/>
);

EmailField.propTypes = {
field: PropTypes.shape({}),
form: PropTypes.shape({}),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]),
placeholder: PropTypes.string,
disabled: PropTypes.bool,
};

export default EmailField;

+ 74
- 0
components/sass-components/InputFields/NumberField.js 查看文件

@@ -0,0 +1,74 @@
import React from 'react';
import PropTypes from 'prop-types';

import BaseInputField from './BaseInputField';
import {
PERIOD_SYMBOL,
COMMA_SYMBOL,
PLUS_SYMBOL,
MINUS_SYMBOL,
NUMPAD_PERIOD_SYMBOL,
NUMPAD_MINUS_SYMBOL,
NUMPAD_PLUS_SYMBOL,
DOWN_ARROW_KEYCODE,
UP_ARROW_KEYCODE,
} from '../../constants/keyCodeConstants';

const NumberField = ({
field,
form,
label,
placeholder,
disabled,
preventAllExceptNumbers,
...props
}) => {
const onKeydownHandler = (event) => {
if (preventAllExceptNumbers) {
if (
event.keyCode === PERIOD_SYMBOL ||
event.keyCode === COMMA_SYMBOL ||
event.keyCode === NUMPAD_PERIOD_SYMBOL ||
event.keyCode === DOWN_ARROW_KEYCODE ||
event.keyCode === UP_ARROW_KEYCODE
) {
event.preventDefault();
}
}

if (
event.keyCode === PLUS_SYMBOL ||
event.keyCode === MINUS_SYMBOL ||
event.keyCode === NUMPAD_MINUS_SYMBOL ||
event.keyCode === NUMPAD_PLUS_SYMBOL ||
event.keyCode === DOWN_ARROW_KEYCODE ||
event.keyCode === UP_ARROW_KEYCODE
) {
event.preventDefault();
}
};

return (
<BaseInputField
type="number"
label={label}
placeholder={placeholder}
disabled={disabled}
form={form}
field={field}
{...props}
onKeyDown={(event) => onKeydownHandler(event)}
/>
);
};

NumberField.propTypes = {
field: PropTypes.shape({}),
form: PropTypes.shape({}),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]),
placeholder: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
disabled: PropTypes.bool,
preventAllExceptNumbers: PropTypes.bool,
};

export default NumberField;

+ 74
- 0
components/sass-components/InputFields/PasswordField.js 查看文件

@@ -0,0 +1,74 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';

import BaseInputField from './BaseInputField';
import PasswordStrength from './PasswordStrength';

const PasswordField = ({
field,
form,
label,
placeholder,
disabled,
shouldTestPasswordStrength,
autoFocus,
...props
}) => {
const [passwordValue, setPasswordValue] = useState('');
const [isCapsLockOn, setIsCapsLockOn] = useState(false);

const onChange = (e) => {
if (shouldTestPasswordStrength) {
const { value } = e.target;
setPasswordValue(value);
}

field.onChange(e);
};

const onKeyDown = (keyEvent) => {
if (keyEvent.getModifierState('CapsLock')) {
setIsCapsLockOn(true);
} else {
setIsCapsLockOn(false);
}
};

return (
<div className="c-password">
<BaseInputField
type="password"
label={label}
placeholder={placeholder}
disabled={disabled}
form={form}
field={field}
{...props}
onChange={onChange}
autoFocus={autoFocus}
onKeyDown={onKeyDown}
isCapsLockOn={isCapsLockOn}
/>
{shouldTestPasswordStrength && (
<PasswordStrength
passwordValue={passwordValue}
shouldTestPasswordStrength
/>
)}
</div>
);
};

PasswordField.propTypes = {
field: PropTypes.shape({
onChange: PropTypes.func,
}),
form: PropTypes.shape({}),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]),
placeholder: PropTypes.string,
disabled: PropTypes.bool,
shouldTestPasswordStrength: PropTypes.bool,
autoFocus: PropTypes.bool,
};

export default PasswordField;

+ 130
- 0
components/sass-components/InputFields/PasswordStrength.js 查看文件

@@ -0,0 +1,130 @@
import React, { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import owasp from 'owasp-password-strength-test';
import i18next from 'i18next';

owasp.config({
minOptionalTestsToPass: 3,
});

const passwordStrengthOptions = [
{
strength: 'weak',
color: '#FF5028',
},
{
strength: 'average',
color: '#FDB942',
},
{
strength: 'good',
color: '#06BEE7',
},
{
strength: 'strong',
color: '#00876A',
},
];

/**
* User must pass a required test and at least 3 optional.
* @param result - owasp result
* @returns {number} - index of password strength 0-3
*/
function getPasswordStrengthIndex(result) {
// requirement for strong password is required test passed and at least 3 optional tests
if (result.strong) {
return 3;
}

if (!result.strong && result.optionalTestsPassed >= 3) {
return 2;
}

if (result.optionalTestsPassed <= 0) {
return 0;
}

return result.optionalTestsPassed - 1;
}

const PasswordStrength = ({
shouldTestPasswordStrength,
passwordValue,
passwordStrengthTestsRequired,
}) => {
const strengthContainer = useRef(null);
const [passwordStrength, setPasswordStrength] = useState({
width: 0,
color: 'red',
});
const [error, setError] = useState('');

useEffect(() => {
if (shouldTestPasswordStrength && passwordValue) {
const bBox = strengthContainer.current.getBoundingClientRect();
const result = owasp.test(passwordValue);

const passwordStrengthIndex = getPasswordStrengthIndex(result);
const passwordOption = passwordStrengthOptions[passwordStrengthIndex];

const width = !passwordValue
? 0
: (bBox.width * (passwordStrengthIndex + 1)) /
passwordStrengthTestsRequired;

setPasswordStrength({ width, color: passwordOption.color });
const strength = i18next.t(`password.${passwordOption.strength}`);
setError(i18next.t('login.passwordStrength', { strength }));
}
}, [
passwordValue,
shouldTestPasswordStrength,
passwordStrengthTestsRequired,
]);

if (!shouldTestPasswordStrength || !passwordValue) {
return null;
}

const renderError = () => {
if (!error) {
return null;
}
return (
<div
className="c-input--error"
style={{
color: passwordStrength.color,
}}
>
{error}
</div>
);
};
return (
<div ref={strengthContainer} className="c-password-strength__container">
<div className="c-password-strength__line--wrapper">
<div
className="c-password-strength__line"
style={{
backgroundColor: passwordStrength.color,
width: passwordStrength.width,
}}
/>
</div>
{renderError()}
</div>
);
};
PasswordStrength.propTypes = {
shouldTestPasswordStrength: PropTypes.bool,
passwordValue: PropTypes.string,
passwordStrengthTestsRequired: PropTypes.number,
};

PasswordStrength.defaultProps = {
passwordStrengthTestsRequired: 4,
};

export default PasswordStrength;

+ 45
- 0
components/sass-components/InputFields/PercentageField.js 查看文件

@@ -0,0 +1,45 @@
import React from 'react';
import PropTypes from 'prop-types';
import NumberFormat from 'react-number-format';

import TextField from './TextField';

const PercentageField = ({ field, ...props }) => {
const handleOnChange = (percentageField) => {
const { floatValue } = percentageField;

if (!props.onChange) {
throw Error('Provide an onChange handler');
}
if (floatValue > 100) {
return props.onChange('100');
}

if (floatValue <= 0 || !floatValue) {
return props.onChange('0');
}

return props.onChange(floatValue.toString());
};

return (
<NumberFormat
format="###%"
value={field.value}
customInput={TextField}
field={field}
{...props}
onValueChange={handleOnChange}
onChange={() => {}}
/>
);
};

PercentageField.propTypes = {
onChange: PropTypes.func,
field: PropTypes.shape({
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
}),
};

export default PercentageField;

+ 49
- 0
components/sass-components/InputFields/PhoneNumberField.js 查看文件

@@ -0,0 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ErrorMessage, useField } from 'formik';
import PhoneInput from 'react-phone-number-input';
import 'react-phone-number-input/style.css';

const PhoneNumberField = ({ label, ...props }) => {
const [field, meta] = useField(props);
const inputErrorClassName =
meta.error && meta.touched ? 'c-input--error' : '';

return (
<div className={`c-input c-phone-number ${inputErrorClassName}`}>
{!!label && (
<label className="c-input__label" htmlFor={field.name}>
{label}
</label>
)}
<PhoneInput
international
defaultCountry="US"
{...field}
{...props}
onChange={(value) => {
props.onPhoneChange(value);
}}
countryOptionsOrder={['US']}
/>
<ErrorMessage name={field.name}>
{(errorMessage) => (
<span className="c-input__error">{errorMessage}</span>
)}
</ErrorMessage>
</div>
);
};

PhoneNumberField.propTypes = {
field: PropTypes.shape({
name: PropTypes.string,
}),
form: PropTypes.shape({}),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]),
disabled: PropTypes.bool,
onChange: PropTypes.func,
onPhoneChange: PropTypes.func,
};

export default PhoneNumberField;

+ 54
- 0
components/sass-components/InputFields/Radio.js 查看文件

@@ -0,0 +1,54 @@
import PropTypes from 'prop-types';
import React from 'react';
import { ReactComponent as RadioOff } from '../../../assets/images/svg/radio-off.svg';
import { ReactComponent as RadioOn } from '../../../assets/images/svg/radio-on.svg';

const Checkbox = ({
className,
children,
name,
checked,
field,
value,
selected,
id,
}) => (
<label
htmlFor={name}
className={`c-radio ${selected ? 'c-radio--selected' : ''} ${
className || ''
}`}
>
<input
name={name}
id={id}
className="c-radio__field"
type="radio"
checked={checked}
value={value}
{...field}
/>
<div className="c-radio__indicator">
{selected ? (
<RadioOn className="c-radio__icon" />
) : (
<RadioOff className="c-radio__icon" />
)}
</div>
<div className="c-radio__text">{children}</div>
</label>
);

Checkbox.propTypes = {
children: PropTypes.node,
checked: PropTypes.bool,
name: PropTypes.string,
field: PropTypes.shape({}),
form: PropTypes.shape({}),
className: PropTypes.string,
value: PropTypes.string,
selected: PropTypes.bool,
id: PropTypes.string,
};

export default Checkbox;

+ 37
- 0
components/sass-components/InputFields/Search.js 查看文件

@@ -0,0 +1,37 @@
import React from 'react';
import PropTypes from 'prop-types';

import BaseInputField from './BaseInputField';

const Search = ({
field,
form,
label,
placeholder,
disabled,
className,
...props
}) => (
<BaseInputField
type="text"
label=""
placeholder={placeholder}
disabled={disabled}
form={form}
field={field}
isSearch
className={className}
{...props}
/>
);

Search.propTypes = {
field: PropTypes.shape({}),
form: PropTypes.shape({}),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]),
placeholder: PropTypes.string,
disabled: PropTypes.bool,
className: PropTypes.string,
};

export default Search;

+ 122
- 0
components/sass-components/InputFields/SelectField.js 查看文件

@@ -0,0 +1,122 @@
import { ErrorMessage, useField } from 'formik';
import PropTypes from 'prop-types';
import React, { useEffect } from 'react';
import Select, { components, createFilter } from 'react-select';
import { ReactComponent as FilledChevronDown } from '../../../assets/images/svg/filled-chevron-down.svg';

const SelectField = ({
label,
disabled,
options,
link,
defaultSelected = null,
dropdownFullHeight,
selectOption,
...props
}) => {
const [field, meta, helpers] = useField(props);

const filterConfig = {
ignoreCase: true,
ignoreAccents: true,
trim: true,
matchFrom: 'start',
};

useEffect(() => {
if (defaultSelected) {
helpers.setValue(defaultSelected);
}
}, [defaultSelected]); // eslint-disable-line

const DropdownIndicator = (props) =>
components.DropdownIndicator && (
<components.DropdownIndicator {...props}>
<FilledChevronDown />
</components.DropdownIndicator>
);

function styles() {
let style = 'c-input';

if (meta.error && meta.touched) {
style += ` c-input--error`;
}

if (dropdownFullHeight) {
style += ` c-input--dropdown-full-height`;
}

return style;
}

return (
<div className={styles()}>
{!!label && (
<label className="c-input__label" htmlFor={field.name}>
{label}
</label>
)}
{!!link && <div className="c-input__link">{link}</div>}
<Select
defaultValue={defaultSelected || options[0]}
components={{ DropdownIndicator }}
isSearchable={false}
classNamePrefix="c-select"
options={options}
isDisabled={disabled}
{...field}
{...props}
onBlur={(e) => {
helpers.setTouched(true);
field.onBlur(e);
}}
onChange={(selectedOption) => {
helpers.setValue(selectedOption);

if (props.onChange) {
props.onChange();
}

if (selectOption) {
selectOption(selectedOption);
}
}}
filterOption={createFilter(filterConfig)}
/>
<ErrorMessage name={field.name}>
{(errorMessage) => {
if (typeof errorMessage === 'string') {
return <span className="c-input__error">{errorMessage}</span>;
}
return <span className="c-input__error">{errorMessage.value}</span>;
}}
</ErrorMessage>
</div>
);
};

SelectField.propTypes = {
field: PropTypes.shape({
name: PropTypes.string,
}),
form: PropTypes.shape({}),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]),
disabled: PropTypes.bool,
options: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
})
),
onChange: PropTypes.func,
link: PropTypes.node,
defaultSelected: PropTypes.shape({
label: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
}),
dropdownFullHeight: PropTypes.bool,
selectOption: PropTypes.func,
};

export default SelectField;

+ 72
- 0
components/sass-components/InputFields/TextField.js 查看文件

@@ -0,0 +1,72 @@
import React from 'react';
import PropTypes from 'prop-types';

import BaseInputField from './BaseInputField';
import {
BACKSPACE_KEYCODE,
TAB_KEYCODE,
RIGHT_ARROW_KEYCODE,
LEFT_ARROW_KEYCODE,
} from '../../constants/keyCodeConstants';

const TextField = ({
field,
form,
label,
placeholder,
disabled,
centerText,
autoFocus,
preventAllExceptNumbers,
...props
}) => {
const onKeydownHandler = (event) => {
if (preventAllExceptNumbers) {
if (
event.keyCode === BACKSPACE_KEYCODE ||
event.keyCode === TAB_KEYCODE ||
event.keyCode === RIGHT_ARROW_KEYCODE ||
event.keyCode === LEFT_ARROW_KEYCODE
) {
return;
}

if (
(event.keyCode < 58 && event.keyCode > 47) ||
(event.keyCode < 106 && event.keyCode > 95)
) {
return;
}

event.preventDefault();
}
};

return (
<BaseInputField
autoFocus={autoFocus}
type="text"
label={label}
placeholder={placeholder}
disabled={disabled}
form={form}
field={field}
centerText={centerText}
{...props}
onKeyDown={(event) => onKeydownHandler(event)}
/>
);
};

TextField.propTypes = {
field: PropTypes.shape({}),
form: PropTypes.shape({}),
label: PropTypes.string,
placeholder: PropTypes.string,
disabled: PropTypes.bool,
centerText: PropTypes.bool,
autoFocus: PropTypes.bool,
preventAllExceptNumbers: PropTypes.bool,
};

export default TextField;

+ 26
- 0
components/sass-components/Loader/BlockSectionLoader.js 查看文件

@@ -0,0 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';

const BlockSectionLoader = ({ children, isLoading, fullHeight, noShadow }) => (
<div
className={`c-loader__wrapper c-loader__wrapper--block ${
fullHeight ? 'c-loader__wrapper--full-height' : ''
} ${noShadow ? 'c-loader__wrapper--no-shadow' : ''}`}
>
{children}
{isLoading && (
<div className="c-loader">
<div className="c-loader__icon" />
</div>
)}
</div>
);

BlockSectionLoader.propTypes = {
children: PropTypes.node,
isLoading: PropTypes.bool,
fullHeight: PropTypes.bool,
noShadow: PropTypes.bool,
};

export default BlockSectionLoader;

+ 11
- 0
components/sass-components/Loader/FullPageLoader.js 查看文件

@@ -0,0 +1,11 @@
import React from 'react';

const FullPageLoader = () => {
return (
<div className="c-loader c-loader--page">
<div className="c-loader__icon" />
</div>
);
};

export default FullPageLoader;

+ 20
- 0
components/sass-components/Loader/SectionLoader.js 查看文件

@@ -0,0 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';

const SectionLoader = ({ children, isLoading }) => (
<div className="c-loader__wrapper">
{children}
{isLoading && (
<div className="c-loader">
<div className="c-loader__icon" />
</div>
)}
</div>
);

SectionLoader.propTypes = {
children: PropTypes.node,
isLoading: PropTypes.bool,
};

export default SectionLoader;

+ 13
- 0
components/sass-components/Section/Section.js 查看文件

@@ -0,0 +1,13 @@
import React from 'react';
import PropType from 'prop-types';

const Section = ({ children, className }) => (
<section className={`l-section ${className || ''}`}>{children}</section>
);

Section.propTypes = {
children: PropType.node,
className: PropType.string,
};

export default Section;

+ 46
- 0
components/sass-components/styles/_base.scss 查看文件

@@ -0,0 +1,46 @@
body {
margin: 0;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-anchor: none;
}

* {
box-sizing: border-box;
}

html {
min-height: 100%;
font-size: 16px;

@include media-below($bp-xxl) {
font-size: 14px;
}

@include media-below($bp-xs) {
font-size: 13px;
}

@include media-below($bp-xxs) {
font-size: 10.5px;
}
}

html,
body,
#root {
@include flex-column;
flex: 1 0 auto;
}

input[type='search']::-webkit-search-decoration,
input[type='search']::-webkit-search-cancel-button,
input[type='search']::-webkit-search-results-button,
input[type='search']::-webkit-search-results-decoration {
-webkit-appearance: none;
}

ul {
list-style: none;
padding: 0;
}

+ 7
- 0
components/sass-components/styles/_functions.scss 查看文件

@@ -0,0 +1,7 @@
@function pxToRem($target, $context: $base-font-size) {
@return ($target / $context) * 1rem;
}

@function pxToRemMd($target, $context: $base-font-size-md) {
@return ($target / $context) * 1rem;
}

+ 17
- 0
components/sass-components/styles/_layout.scss 查看文件

@@ -0,0 +1,17 @@
.l-page {
@include flex-column;
flex: 1 1 auto;
padding-bottom: 7rem;
position:relative;
@include media-below($bp-xl) {
padding-bottom: 4rem;
}
}

.l-section {
padding: 0 3.25rem;

@include media-below($bp-xl) {
padding: 0;
}
}

+ 81
- 0
components/sass-components/styles/_mixins.scss 查看文件

@@ -0,0 +1,81 @@
@mixin desktop {
@media (min-width: 1280px) {
@content;
}
}

@mixin desktop-lg {
@media (min-width: 1480px) {
@content;
}
}

@mixin tablet {
@media (max-width: 1024px) {
@content;
}
}

@mixin media-up($media) {
@media only screen and (min-width: $media) {
@content;
}
}

@mixin media-below($media) {
@media only screen and (max-width: #{$media - 0.02px}) {
@content;
}
}

@mixin media-between($mediaMin, $mediaMax) {
@media screen and (min-width: $mediaMin) and (max-width: #{$mediaMax - 0.02px}) {
@content;
}
}

@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}

@mixin flex-column {
display: flex;
flex-direction: column;
}

@mixin button-clear {
border: none;
padding: 0;
background-color: transparent;
}

@mixin outline-none {
&,
&:active,
&:focus {
outline: none;
}
}

@mixin reset-position {
position: relative;
top: initial;
left: initial;
right: initial;
bottom: initial;
}

@mixin text-ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

@mixin line-clamp($lines) {
display: -webkit-box;
-webkit-line-clamp: $lines;
-webkit-box-orient: vertical;
overflow: hidden;
}

+ 244
- 0
components/sass-components/styles/_overwrite.scss 查看文件

@@ -0,0 +1,244 @@
// Overwrite
.ais-ClearRefinements-button {
color: $grey-11;
font-size: pxToRem(14px);
letter-spacing: 0;
line-height: 1.15;
background-color: transparent;
border: none;
text-decoration: underline;
position: relative;
transition: all 0.2s;
outline: none;
cursor: pointer;

&[disabled] {
pointer-events: none;
opacity: 0.5;
cursor: auto;
}

&:hover {
color: $color-primary-light;
}

&:active {
color: $color-primary-dark;
}
}

.ais-RefinementList {
margin-bottom: pxToRem(32px);
margin-left: pxToRem(16px);

&.c-filter__refinement--closed {
display: none;
}
}

.ais-RefinementList.expanded {
.ais-RefinementList-showMore::before {
transform: rotate(180deg);
}
}

.ais-RefinementList-showMore {
color: $color-primary;
font-size: pxToRem(14px);
font-weight: 600;
letter-spacing: 0;
line-height: 1.56;
text-align: center;
background-color: transparent;
border: none;
position: relative;
margin-left: pxToRem(20px);
outline: none;
transition: all ease-in-out 0.3s;
cursor: pointer;

&[disabled] {
display: none;
}

&:hover {
color: $color-primary-light;
}

&:active {
color: $color-primary-dark;
}

&::before {
content: '';
background-image: url('../images/chevron-down.svg');
fill: $color-primary;
-webkit-text-stroke-color: $color-primary;
background-position: center;
width: pxToRem(20px);
height: pxToRem(20px);
position: absolute;
left: pxToRem(-22px);
transition: all 0.2s;
}
}

.ais-SearchBox {
display: flex;
justify-content: flex-end;
margin-bottom: pxToRem(24px);
}

.ais-SearchBox-input {
border: none;
color: $blue;
font-size: pxToRem(16px);
letter-spacing: 0;
line-height: 1.5;
outline: none;
-moz-appearance: none;
-webkit-appearance: none;
flex-grow: 1;

&::placeholder {
color: $blue;
font-size: pxToRem(16px);
}

@include media-below($bp-xl) {
font-size: pxToRemMd(16px);

&::placeholder {
font-size: pxToRemMd(16px);
}
}
}

.ais-SearchBox-form {
border: 1px solid $grey-6;
border-radius: $border-radius;
overflow: hidden;
padding: 0 pxToRem(12px);
height: pxToRem(33px);
align-items: center;
display: flex;
justify-content: space-between;
min-width: pxToRem(340px);
}

.ais-SearchBox-submit,
.ais-SearchBox-reset {
border: none;
background: transparent;
outline: none;
height: pxToRem(16px);

> svg {
color: $blue-1;
fill: $blue-1;
}
}

.ais-SearchBox-submitIcon {
width: pxToRem(14px);
height: pxToRem(14px);
color: $blue-1;
fill: $blue-1;
}

.ais-SearchBox-resetIcon {
width: pxToRem(14px);
height: pxToRem(14px);
}

.ais-SearchBox-reset {
margin-left: pxToRem(10px);
cursor: pointer;
}

.c-plaid-link {
padding: 0 !important;
background: transparent !important;
border-width: 0 !important;
border-radius: 0 !important;
box-shadow: $box-shadow !important;

&.c-plaid-link--select-card {
margin-top: pxToRem(40px);

.c-select-card__button {
margin-top: 0;
}
}
}

.ais-InfiniteHitsWrap {
min-height: pxToRem(200px);
}

.ais-Highlight-highlighted {
background: #fff1d6;
font-style: normal;
}

.acsb-trigger {
display: none !important;
visibility: hidden !important;
width: 0 !important;
height: 0 !important;
}

.ais-CurrentRefinements-list {
display: flex;
flex-wrap: wrap;

> :not(:last-child) {
margin-right: pxToRem(16px);
}
}

.ais-CurrentRefinements-item {
border-radius: $border-radius;
background-color: $dark-blue;
padding: pxToRem(4px) pxToRem(8px);
flex-shrink: 0;
margin-bottom: pxToRem(16px);
}

.ais-CurrentRefinements-item-link {
font-size: pxToRem(16px);
line-height: 1.5;
font-weight: 600;
color: $white;
display: flex;
align-items: center;
text-decoration: none;
}

.ais-CurrentRefinements-close {
color: $white;
width: pxToRem(24px);
margin-left: pxToRem(8px);
}

.recharts-surface {
overflow: visible;
}

.recharts-cartesian-axis-tick-value {
color: #9aa1a9;
font-size: 10px;
letter-spacing: 0;
line-height: 20px;
}
.recharts-tooltip-wrapper:empty{
display: 'none',
}
.recharts-text{
&.recharts-pie-label-text{
font-size: 14px;
@include media-below($bp-xl) {
font-size: 12px;
}
}
}

+ 127
- 0
components/sass-components/styles/_reset.scss 查看文件

@@ -0,0 +1,127 @@
/**
* Reset
*
*/

*,
*:before,
*:after {
box-sizing: border-box;
}

*,
body,
button,
input,
textarea,
select {
text-rendering: optimizeLegibility;
-moz-osx-font-smoothing: grayscale;
}

body,
div,
dl,
dt,
dd,
ul,
ol,
li,
h1,
h2,
h3,
h4,
h5,
h6,
pre,
form,
fieldset,
button,
input,
textarea,
p,
blockquote,
th,
td {
margin: 0;
padding: 0;
}

table {
border-collapse: collapse;
border-spacing: 0;
}

fieldset,
img {
border: 0;
}

address,
caption,
cite,
code,
dfn,
em,
th,
var {
font-style: normal;
font-weight: normal;
}

strong {
font-weight: 800;
}

ol,
ul {
list-style: none;
}

caption,
th {
text-align: left;
}

q:before,
q:after {
content: '';
}

abbr,
acronym {
border: 0;
}

svg {
flex-shrink: 0;
}

textarea,
input:matches([type='email'], [type='number'], [type='password'], [type='search'], [type='tel'], [type='text'], [type='url']) {
-webkit-appearance: none;

&::-webkit-autofill,
&::-webkit-contacts-auto-fill-button,
&::-webkit-credentials-auto-fill-button {
visibility: hidden;
user-select: none;
display: none !important;
position: absolute;
}
}

input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;

&::-webkit-autofill,
&::-webkit-contacts-auto-fill-button,
&::-webkit-credentials-auto-fill-button {
visibility: hidden;
user-select: none;
display: none !important;
position: absolute;
}
}

+ 57
- 0
components/sass-components/styles/_typography.scss 查看文件

@@ -0,0 +1,57 @@
body,
div,
dl,
dt,
dd,
ul,
ol,
li,
h1,
h2,
h3,
h4,
h5,
h6,
pre,
form,
fieldset,
button,
input,
textarea,
p,
blockquote,
th,
td {
font-family: $font-family;
}

p {
vertical-align: middle;
display: inline-block;
word-break: break-word;
font-size: pxToRem(16px);
line-height: 1.5;

@include media-below($bp-md) {
font-size: pxToRemMd(16px);
}
}

a {
font-size: inherit;
line-height: inherit;
color: inherit;
}

strong {
font-weight: bold;
}

h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 500;
}

+ 39
- 0
components/sass-components/styles/_utility.scss 查看文件

@@ -0,0 +1,39 @@
.u-mr-24 {
margin-right: 24px;
}

.u-ml-32 {
margin-left: pxToRem(32px);
}

.u-position-relative {
position: relative;
}

.u-column {
@include flex-column;
}

.u-display-none {
display: none;
}

.u-superscript {
font-size: pxToRem(14px);
font-weight: medium;
}

.u-text-align-right {
text-align: right;
}

.u-hide {
width: 0;
height: 0;
visibility: hidden;
display: none;
position: fixed;
top: -20px;
right: -20px;
z-index: -1;
}

+ 72
- 0
components/sass-components/styles/_variables.scss 查看文件

@@ -0,0 +1,72 @@
$base-font-size: 16px;
$base-font-size-md: 14px;

$font-family: 'Avenir Next';

// Colors
$color-primary: #024f86;
$color-primary-light: #024f86;
$color-primary-dark: #003246;
$yellow: #ffeac1;
$white: #ffffff;
$grey: #f4f4f4;
$grey-1: #ccced0;
$grey-2: #fafafa;
$grey-3: #c2c5c6;
$grey-4: #d8d8d8;
$grey-5: #808285;
$grey-6: #c9d6db;
$grey-7: rgba(128, 130, 133, 0.5);
$grey-8: rgba(201, 214, 219, 0.25);
$grey-9: #ebeff2;
$grey-10: #f0f5f6;
$grey-11: #8b8b8b;
$grey-12: #b0bfc540;
$grey-13: #9d9ea4;
$grey-14: #f7fafa;
$grey-15: #ebf2f2;
$dark-blue: #003246;
$blue: #4e7a8c;
$blue-1: #6e8fae;
$blue-2: #024f86;
$blue-3: #0f85ec;
$blue-4: #5c7e9f;
$blue-5: #dde5e7;
$black: #000;
$black-1: rgba(0, 0, 0, 0.3);
$black-2: rgba(32, 38, 43, 0.9);
$black-4: #172029;
$black-5: #272727;
$black-6: #1d2731;
$red: #ff5028;
$success: #09846b;
$success-1: #00876a;
$success-2: #008a68;

// Shadow
$button-shadow-hover: 0 5px 6px 0 rgba(112, 120, 135, 0.24);
$button-shadow-pressed: 0 2px 4px 0 rgba(112, 120, 135, 0.24);
$box-shadow: 0 3px 8px 0 rgba(112, 120, 135, 0.24);
$account-dropdown-shadow: 0 6px 38px 0 rgba(112, 120, 135, 0.56);

// Border Radius
$border-radius: 4px;
$border-radius-md: 8px;

// Breakpoints
$bp-xxs: 325px;
$bp-xs: 400px;
$bp-sm: 576px;
$bp-md: 768px;
$bp-lg: 992px;
$bp-xl: 1200px;
$bp-xxl: 1350px;

// z-index
$index-xxs: 1;
$index-xs: 2;
$index-sm: 3;
$index-md: 4;
$index-lg: 5;
$index-xl: 6;
$index-xxl: 7;

+ 60
- 0
components/sass-components/styles/components/_app-button.scss 查看文件

@@ -0,0 +1,60 @@
.c-button {
display: inline-flex;
align-items: center;
border-radius: $border-radius;
background-color: $color-primary;
box-shadow: 0 2px 4px 0 rgba(112, 120, 135, 0.24);
border: transparent;
padding: 8px 0;
color: $white;
width: 100%;
text-align: center;
justify-content: center;
font-family: "Avenir Next";
font-size: pxToRem(18px);
font-weight: 600;
letter-spacing: 0;
line-height: 26px;
outline: none;
text-transform: uppercase;
transition: all 0.3s ease-in-out;
cursor: pointer;

&.c-button--clean {
background: transparent;
border: 1px solid $color-primary;
color: $color-primary;

&:hover {
border-color: $color-primary-light;
color: $color-primary-light;
background-color: transparent;
}

&:active {
border-color: $color-primary-dark;
color: $color-primary-dark;
}
}

&.c-button--dropdown {
justify-content: flex-end;
padding: 8px 14px;
background-image: url("../../images/down.svg");
background-repeat: no-repeat;
background-position: 8% 50%;
}

&[disabled] {
pointer-events: none;
opacity: 0.5;
}

&:hover {
background-color: $color-primary-light;
}

&:active {
background-color: $color-primary-dark;
}
}

+ 45
- 0
components/sass-components/styles/components/_auth-card.scss 查看文件

@@ -0,0 +1,45 @@
.c-auth-card {
max-width: pxToRem(624px);
width: 100%;
box-shadow: $box-shadow;
border-radius: $border-radius;
border: 1px solid $color-primary-light;
padding: pxToRem(24px) pxToRem(40px) pxToRem(32px);

@include media-below($bp-md) {
border: none;
box-shadow: none;
padding: 0;
max-width: 100%;

.c-auth-card__title {
text-align: left;
font-size: pxToRemMd(36px);
margin-bottom: pxToRemMd(6px);
}

.c-auth-card__subtitle {
font-size: pxToRemMd(16px);
text-align: left;
}
}
}

.c-auth-card__title {
text-align: left;
font-size: pxToRem(36px);
line-height: 1.22;
color: $dark-blue;
font-weight: 400;
margin-bottom: pxToRem(16px);
}

.c-auth-card__subtitle {
font-size: pxToRem(16px);
line-height: 1.5;
letter-spacing: 0;
color: $color-primary;
text-align: left;
width: 100%;
font-weight: 600;
}

+ 23
- 0
components/sass-components/styles/components/_auth.scss 查看文件

@@ -0,0 +1,23 @@
.c-auth {
@include flex-center;
flex-direction: column;
padding-bottom: pxToRem(56px);

@include media-below($bp-md) {
padding: 0 pxToRemMd(24px) pxToRemMd(92px);

.c-auth__title {
margin: pxToRemMd(48px) auto;
font-size: pxToRemMd(24px);
line-height: 1.35;
}
}
}

.c-auth__title {
margin: pxToRem(56px) auto pxToRem(80px);
font-size: pxToRem(36px);
line-height: 1.22;
color: $dark-blue;
font-weight: bold;
}

+ 173
- 0
components/sass-components/styles/components/_button.scss 查看文件

@@ -0,0 +1,173 @@
.c-btn {
@include outline-none;
@include button-clear;
@include flex-center;
font-size: pxToRem(18px);
line-height: 1.35;
padding: pxToRem(8px) pxToRem(8px);
border-radius: $border-radius;
box-shadow: $button-shadow-pressed;
color: inherit;
font-weight: 600;
letter-spacing: 0;
text-align: center;
text-transform: uppercase;
user-select: none;
white-space: nowrap;
min-width: pxToRem(120px);
flex-shrink: 0;
cursor: pointer;
transition: background-color 0.2s, color 0.2s;

&:disabled {
opacity: 0.5;
cursor: auto;
}

&.c-btn--primary {
background-color: $color-primary;
color: $white;
border: 1px solid $color-primary;

&:disabled {
&:hover {
background-color: $color-primary;
box-shadow: none;
}
}

&:hover {
background-color: $color-primary-light;
box-shadow: $button-shadow-hover;
}

&:focus,
&:active {
background-color: $color-primary-dark;
box-shadow: $button-shadow-pressed;
}
}

&.c-btn--primary-outlined {
background-color: transparent;
color: $color-primary;
border: 1px solid $color-primary;

&:disabled {
&:hover {
color: $color-primary;
border: 1px solid $color-primary;
}
}

&:hover {
color: $color-primary;
border: 1px solid $color-primary;
}

&:focus,
&:active {
color: $color-primary;
border: 1px solid $color-primary;
}
}

&.c-btn--blue {
background-color: $blue-3;
color: $white;
background-color: $blue-3;
}

&.c-btn--white {
background-color: $white;
color: $grey-3;
border: 1px solid $grey-4;
box-shadow: $box-shadow;

&:disabled {
&:hover {
background-color: $white;
color: $grey-3;
}
}

&:hover {
color: $grey-5;
}

&:focus,
&:active {
background-color: $grey;
}
}

&.c-btn--primary-clear {
background-color: transparent;
color: $color-primary;
border: none;
box-shadow: none;
padding: 0;
}

&.c-btn--auto {
min-width: auto;
}

&.c-btn--sm {
font-size: pxToRem(16px);
line-height: 1.5;
padding: pxToRem(4px) pxToRem(15px);
}

&.c-btn--capitalize {
text-transform: capitalize;
}

&.c-btn--bank-acount-card {
padding: 0 pxToRem(16px);
min-height: pxToRem(32px);
min-width: pxToRem(120px);
font-size: pxToRem(16px);
line-height: 1.5;
}

&.c-btn--hidden {
visibility: hidden;
height: 0;
}

@include media-below($bp-md) {
padding: pxToRemMd(4px) pxToRemMd(25px);
font-size: pxToRemMd(16px);
line-height: 1.5;
min-width: pxToRemMd(80px);

&.c-btn--auth {
padding: pxToRemMd(12px) pxToRemMd(25px);
line-height: 1.35;
font-size: pxToRemMd(18px);
}

&.c-btn--sm {
padding: pxToRemMd(4px) pxToRemMd(15px);
}

&.c-btn--bank-acount-card {
flex-grow: 1;
min-height: pxToRemMd(40px);
padding: pxToRemMd(8px) pxToRemMd(16px);
font-size: pxToRemMd(18px);
line-height: 1.33;
}

&.c-btn--lg {
padding: pxToRemMd(7.5px) pxToRemMd(15px);
font-size: pxToRemMd(18px);
line-height: 1.5;
}
}

@include media-below($bp-xs) {
white-space: unset;
}
}

+ 46
- 0
components/sass-components/styles/components/_error-page.scss 查看文件

@@ -0,0 +1,46 @@
.c-error-page {
margin-top: pxToRem(120px);

@include media-below($bp-md) {
margin-top: pxToRemMd(120px);

.c-error-page__title {
font-size: pxToRemMd(160px);
margin-bottom: pxToRemMd(27px);
}

.c-error-page__text {
margin-bottom: pxToRem(24px);
}
}
}

.c-error-page__content-container {
@include flex-center;
}

.c-error-page__content {
@include flex-column;
align-items: center;
padding: 0 pxToRem(32px);
}

.c-error-page__title {
font-size: pxToRem(160px);
line-height: 1.35;
color: $dark-blue;
margin-bottom: pxToRem(32px);
color: $color-primary;
font-weight: bold;
}

.c-error-page__text {
font-weight: 600;
margin-bottom: pxToRem(24px);
text-align: center;
}

.c-error-page__button {
margin-bottom: pxToRem(16px);
min-width: pxToRem(250px);
}

+ 23
- 0
components/sass-components/styles/components/_forgot-password.scss 查看文件

@@ -0,0 +1,23 @@
.c-reset-security {
padding-top: pxToRem(56px);

@include media-below($bp-md) {
padding-top: pxToRemMd(40px);

.c-reset-security__button {
width: 100%;
margin-top: pxToRemMd(44px);
}
}
}

.c-reset-security__question {
color: $dark-blue;
font-weight: 600;
margin-bottom: pxToRem(20px);
}

.c-reset-security__button {
width: 100%;
margin-top: pxToRem(48px);
}

+ 7
- 0
components/sass-components/styles/components/_icon-button.scss 查看文件

@@ -0,0 +1,7 @@
.c-icon-button {
@include flex-center;
@include outline-none;
@include button-clear;
user-select: none;
cursor: pointer;
}

+ 479
- 0
components/sass-components/styles/components/_input.scss 查看文件

@@ -0,0 +1,479 @@
.c-input {
@include flex-column;
position: relative;

&.c-input--error {
.c-input__field,
.c-select__control,
.c-select__control:hover {
border-color: $red;
}
}

&.c-input--password {
.c-input__icon {
position: absolute;
right: 0;
top: 50%;
transform: translate(0, -50%);
margin-right: pxToRem(12px);
width: pxToRem(24px);
height: pxToRem(24px);
display: flex;

svg {
width: pxToRem(24px);
height: pxToRem(24px);
color: $black;
}
}

.c-input__caps-lock {
position: absolute;
right: 0;
top: 50%;
transform: translate(0, -50%);
margin-right: pxToRem(40px);
width: pxToRem(24px);
height: pxToRem(24px);
display: flex;
width: pxToRem(24px);
height: pxToRem(24px);
color: $black;
}

.c-input__field {
padding-right: pxToRem(72px);
}
}

&.c-input--demi-bold {
.c-input__field {
font-weight: 600;
}
}

&.c-input--search {
position: relative;
width: 100%;

.c-input__icon {
width: pxToRem(24px);
height: pxToRem(24px);
position: absolute;
right: 0;
top: 50%;
transform: translate(0, -50%);
color: $blue-1;
margin-right: pxToRem(12px);
}

&.c-input--search-management {
max-width: pxToRem(344px);
margin-right: pxToRem(24px);

.c-input__field {
height: pxToRem(34px);
font-size: pxToRem(16px);
line-height: 1.5;
letter-spacing: 0;
}
}
}

&.c-input--center-text {
input {
text-align: center;
}
}

@include media-below($bp-xl) {
&.c-input--search {
&.c-input--search-management {
max-width: 100%;
margin-right: pxToRemMd(16px);

.c-input__field {
height: pxToRemMd(32px);
font-size: pxToRemMd(16px);
line-height: 1.5;
letter-spacing: 0;
}
}
}

.c-input__label {
font-size: pxToRemMd(16px);
}

.c-input__field {
font-size: pxToRemMd(16px);
}

.c-input__error {
font-size: pxToRemMd(14px);
}

.c-select__control {
&.c-select__control {
font-size: pxToRemMd(16px);
min-height: 0;

.c-select__input,
.c-select__placeholder {
font-size: pxToRemMd(16px);
}

.c-select__indicator {
> svg {
width: pxToRemMd(16px);
height: pxToRemMd(16px);
}
}
}
}

.c-select__menu {
.c-select__option,
.c-select__menu-notice {
font-size: pxToRemMd(16px);
}
}

.c-input__link {
a,
span {
font-size: pxToRemMd(16px);
}
}

//Overide
.c-password-strength__container {
font-size: pxToRemMd(16px);
}

.c-phone-number {
.PhoneInput {
font-size: pxToRemMd(16px);

&::placeholder {
font-size: pxToRemMd(16px);
}
}

.PhoneInputInput {
font-size: pxToRemMd(16px);
}
}
}

&.c-input--dropdown-full-height {
.c-select__menu {
max-height: initial;
}
}
}

.c-input__label {
color: $blue;
font-size: pxToRem(16px);
font-weight: 600;
letter-spacing: 0;
line-height: 1.75;
margin-bottom: pxToRem(4px);
}

.c-input__field-wrap {
width: 100%;
position: relative;
}

.c-input__field {
@include outline-none;
border: 1px solid $grey-6;
border-radius: $border-radius;
font-size: pxToRem(16px);
line-height: 1.75;
height: pxToRem(50px);
padding: 0 pxToRem(12px);
color: $blue;
background-color: $white;
width: 100%;

&:disabled {
background-color: $grey-8;
border-color: $grey-6;
}

&:focus {
border-color: $color-primary;
}
}

.c-input__error {
position: absolute;
top: 100%;
left: 0;
right: 0;
color: $red;
font-size: pxToRem(14px);
line-height: 1.35;
font-weight: 500;
margin: pxToRem(4px) 0;
}

.c-select__control {
&.c-select__control {
@include outline-none;
border: 1px solid $grey-6;
border-radius: $border-radius;
font-size: pxToRem(16px);
line-height: 1.75;
height: pxToRem(50px);
padding: 0 pxToRem(12px);
color: $blue;
background-color: $white;
box-shadow: none;

&:hover {
border-color: $grey-6;
}

&.c-select__control--is-focused {
border-color: $color-primary;
box-shadow: none;

&:hover {
border-color: $color-primary;
}
&.c-select__control--menu-is-open{
.c-select__indicator {
svg {
transform: rotate(-180deg);
}
}
}
}

.css-1uccc91-singleValue {
color: $blue;
margin: 0;
}

.css-b8ldur-Input {
margin: 0;
}

.c-select__value-container {
height: 100%;
padding: 0;
padding-right: pxToRem(32px);
}

.c-select__input,
.c-select__placeholder {
font-size: pxToRem(16px);
line-height: 1.75;
letter-spacing: 0;
color: $blue;
}

.c-select__indicator-separator {
display: none;
}

.c-select__indicator {
padding: 0;

> svg {
width: pxToRem(16px);
height: pxToRem(16px);
color: $blue;
transform: rotate(0);
transition: transform 0.2s;
}
}

&.c-select__control--is-disabled {
background-color: $grey-8;
}
}
}

.c-select__menu {
@include flex-column;
position: absolute;
top: 100%;
left: 0;
right: 0;
margin-top: pxToRem(4px);
margin-bottom: pxToRem(4px);
border: 1px solid $grey-6;
border-radius: $border-radius;
box-shadow: $box-shadow;
max-height: pxToRem(150px);
overflow: auto;

.c-select__menu-list {
@include flex-column;
padding: 0;
flex-grow: 1;
}

.c-select__option,
.c-select__menu-notice {
padding: pxToRem(12px) pxToRem(15px);
font-size: pxToRem(16px);
line-height: 1.75;
letter-spacing: 0;
color: $blue;
text-align: left;

&:hover {
background-color: $grey-2;
}

&.c-select__option--is-selected {
background-color: $grey-2;
}

&.c-select__option--is-focused {
background-color: $grey-2;
}
}
}

.c-input__link {
position: absolute;
top: 0;
right: 0;

a,
span {
color: $grey-11;
font-size: pxToRem(16px);
letter-spacing: 0;
line-height: 1.15;
text-decoration: underline;
cursor: pointer;
}
}

//Overide
.c-password-strength__container {
margin-top: pxToRem(8px);
font-size: pxToRem(16px);

& .c-password-strength__line--wrapper {
border-radius: 8px;
overflow: hidden;
background-color: $grey;
height: pxToRem(5px);

.c-password-strength__line {
height: pxToRem(5px);
left: 0;
top: 0;
}
}
}

.c-password {
min-height: pxToRem(110px);

@include media-below($bp-xl) {
min-height: pxToRemMd(110px);
}
}

.c-phone-number {
.PhoneInput {
@include outline-none;
box-sizing: border-box;
border: 1px solid $grey-6;
border-radius: $border-radius;
font-size: pxToRem(16px);
line-height: 1.75;
min-height: pxToRem(50px);
color: $blue;
background-color: $white;
box-shadow: none;
width: 100%;
overflow: hidden;

&::placeholder {
font-size: pxToRem(16px);
line-height: 1.75;
}

&:disabled {
background-color: $grey-8;
border-color: $grey-6;
}

&.PhoneInput--focus {
border-color: $color-primary;

.PhoneInputCountry {
border-color: $color-primary;
}
}
}

.PhoneInputCountry {
@include flex-center;
width: pxToRem(96px);
border-right: 1px solid $grey-6;
}

.PhoneInputCountryIcon {
margin-right: pxToRem(16px);
width: auto;
height: auto;
border: none;
}

.PhoneInputCountryIconImg {
width: pxToRem(36px);
object-fit: contain;
}

.PhoneInputCountrySelectArrow {
border: none;
width: 0;
height: 0;
transform: translate(0);
border-left: pxToRem(8px) solid transparent;
border-right: pxToRem(8px) solid transparent;
border-top: pxToRem(8px) solid $blue;
}

.PhoneInputInput {
@include outline-none;
border-color: transparent;
height: 100%;
font-size: pxToRem(16px);
line-height: 1.75;
padding: 0;
color: $blue;
background-color: $white;
width: 100%;
margin: 0;
padding: 0 pxToRem(26px);
height: pxToRem(50px);
}

.PhoneInputCountry {
margin-right: 0;
}

&.c-input--error {
.PhoneInput {
border-color: $red;

.PhoneInputCountry {
border-color: $red;
}
}
}
}

+ 72
- 0
components/sass-components/styles/components/_loader.scss 查看文件

@@ -0,0 +1,72 @@
.c-loader__wrapper {
@include flex-column;
flex: 1 1 auto;
position: relative;
min-height: 0;
min-width: 0;

&.c-loader__wrapper--block {
box-shadow: $box-shadow;

.c-loader {
position: relative;
top: unset;
left: unset;
right: unset;
bottom: unset;
}
}

&.c-loader__wrapper--full-height {
height: 100%;
}

&.c-loader__wrapper--no-shadow {
box-shadow: none;
}

.c-loader {
@include flex-center;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: pxToRem(15px) 0;
background-color: rgba(255, 255, 255, 0.4);
z-index: $index-lg;

&.c-loader--page {
position: fixed;

.c-loader__icon {
border: 20px solid transparent;
width: pxToRem(200px);
height: pxToRem(200px);
border-bottom-color: $color-primary;
border-top-color: $color-primary;
}
}
}

.c-loader__icon {
border-radius: 50%;
border: 10px solid transparent;
border-bottom-color: $color-primary;
border-top-color: $color-primary;
animation: 1s loader-animation linear infinite;
width: pxToRem(100px);
height: pxToRem(100px);
}

@keyframes loader-animation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
}

+ 31
- 0
components/sass-components/styles/components/_login-card.scss 查看文件

@@ -0,0 +1,31 @@
.c-login-card {
border: 1px solid $color-primary-light;
border-radius: $border-radius;
box-shadow: $box-shadow;
max-width: pxToRem(624px);
width: 100%;
margin: pxToRem(28px) auto 0;
padding: pxToRem(36px) pxToRem(40px) pxToRem(32px);
}

.c-login-card__note {
color: $color-primary;
font-weight: 600;
margin-bottom: pxToRem(37px);
}

.c-login-card__form {
display: grid;
grid-row-gap: pxToRem(24px);
}

.c-login-card__submit {
margin-top: pxToRem(24px);
width: 100%;
}

.c-login-card__question {
color: $blue;
font-weight: 600;
margin-bottom: pxToRem(16px);
}

+ 72
- 0
components/sass-components/styles/components/_login.scss 查看文件

@@ -0,0 +1,72 @@
.c-login {
&.c-login--user {
.c-login__form {
.c-input:first-child {
margin-bottom: pxToRem(20px);
}
}
}

@include media-below($bp-xl) {
.c-login__link {
margin-top: pxToRemMd(70px);
}
}

@include media-below($bp-md) {
.c-login__form {
margin: pxToRemMd(36px) 0 0;
}

.c-login__button {
margin-bottom: pxToRemMd(40px);
margin-top: pxToRemMd(36px);
}

.c-login__link {
margin-top: pxToRemMd(80px);
}

&.c-login--user {
.c-login__form {
.c-input:first-child {
margin-bottom: pxToRemMd(20px);
}
}
}
}
}

.c-login__link {
color: $color-primary;
font-weight: 600;
margin-top: pxToRem(40px);
width: max-content;
}

.c-login__form {
margin: pxToRem(36px) 0 0;
> form {
@include flex-column;
}
}

.c-login__button {
width: 100%;
margin-top: pxToRem(68px);
margin-bottom: pxToRem(24px);
}

.c-login__text {
text-align: center;
width: 100%;
color: $blue;

a {
color: $color-primary;
font-weight: bold;
letter-spacing: inherit;
font-size: inherit;
line-height: inherit;
}
}

+ 169
- 0
components/sass-components/styles/components/_modal.scss 查看文件

@@ -0,0 +1,169 @@
$header-height-desktop: pxToRem(80px);
$header-height-mobile: pxToRemMd(74px);

.c-modal-wrap {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: $index-xl;
background-color: $black-1;

&.c-modal-wrap--no-bg {
background-color: transparent;
}

&.c-modal-wrap--over-modal {
background-color: transparent;
z-index: $index-xxl;
}

&.c-modal-wrap--sm {
.c-modal {
max-width: pxToRem(390px);
width: 100%;
}

.c-modal__header {
padding: pxToRem(12px);
}
}

.c-modal__header {
padding: pxToRem(12px);
}

&.c-modal-wrap--md {
.c-modal {
max-width: pxToRem(521px);
width: 100%;
}

.c-modal__header {
padding: pxToRem(12px) pxToRem(20px);
}
}

&.c-modal-wrap--lg {
.c-modal {
max-width: pxToRem(782px);
width: 100%;
}

.c-modal__header {
padding: pxToRem(12px) pxToRem(20px);
}
}

&.c-modal-wrap--close {
display: none;
}

@include media-below($bp-xl) {
&,
&.c-modal-wrap--sm,
&.c-modal-wrap--md {
.c-modal {
margin: $header-height-mobile auto $header-height-mobile;
max-height: calc(100vh - #{2 * $header-height-mobile});
}
}
}

@include media-below($bp-md) {
&,
&.c-modal-wrap--sm,
&.c-modal-wrap--md {
.c-modal__header {
padding: pxToRemMd(16px);
}

.c-modal__title {
font-size: pxToRemMd(16px);
line-height: 1.4;
}

.c-modal__close {
width: pxToRemMd(24px);
height: pxToRemMd(24px);
}

.c-modal__back {
width: pxToRemMd(24px);
height: pxToRemMd(24px);
margin-right: pxToRemMd(8px);
}

.c-modal__back-button {
margin-left: -#{pxToRemMd(8px)};
}

.c-modal,
&.c-modal-wrap--lg .c-modal {
max-width: 100%;
max-height: 100vh;
height: 100%;
margin: 0;
border-radius: 0;
}

&.c-modal-wrap--mobile-modal {
display: flex;
padding: 0 pxToRemMd(20px);

.c-modal {
height: auto;
margin: auto;
border-radius: 2px;
}
}
}
}
}

.c-modal {
@include flex-column;
box-shadow: $box-shadow;
border-radius: $border-radius;
background-color: $white;
margin: $header-height-desktop auto $header-height-desktop;
max-height: calc(100vh - #{2 * $header-height-desktop});
position: relative;
}

.c-modal__header {
display: flex;
align-items: center;
box-shadow: $box-shadow;
z-index: $index-xxs;
}

.c-modal__title {
@include text-ellipsis;
font-size: pxToRem(16px);
font-weight: 600;
line-height: 1.5;
color: $dark-blue;
padding-right: pxToRem(10px);
margin-right: auto;
}

.c-modal__close {
width: pxToRem(16px);
height: pxToRem(16px);
color: $dark-blue;
}

.c-modal__back {
width: pxToRem(16px);
height: pxToRem(16px);
color: $dark-blue;
margin-right: pxToRem(10px);
}

.c-modal__body {
@include flex-column;
flex: 1 1 auto;
overflow: auto;
}

+ 29
- 0
components/sass-components/styles/components/_radio.scss 查看文件

@@ -0,0 +1,29 @@
.c-radio {
display: flex;
cursor: pointer;

&.c-radio--selected {
border-color: $dark-blue;
}
}

.c-radio__field {
display: none;
}

.c-radio__indicator {
margin-top: pxToRem(4px);
margin-right: pxToRem(16px);
}

.c-radio__icon {
width: pxToRem(16px);
height: pxToRem(16px);
}

.c-radio__text {
font-size: pxToRem(14px);
line-height: 1.15;
color: $blue;
user-select: none;
}

+ 2
- 33
pages/_app.js 查看文件

@@ -9,40 +9,9 @@ import { appWithTranslation } from 'next-i18next';
import Head from 'next/head';
import { useState } from 'react';
import Layout from '../components/layout/base-layout/Layout';
import CircularIndeterminate from '../components/loader/route-loader/CircularIndeterminate';
import '../styles/globals.css';

import { useRouter } from 'next/router';
import { useEffect } from 'react';

const LoadingSpinner = () => {
const router = useRouter();

const [loading, setLoading] = useState(false);

useEffect(() => {
const handleStart = (url) => url !== router.asPath && setLoading(true);
const handleComplete = (url) => url === router.asPath && setLoading(false);

router.events.on('routeChangeStart', handleStart);
router.events.on('routeChangeComplete', handleComplete);
router.events.on('routeChangeError', handleComplete);

return () => {
router.events.off('routeChangeStart', handleStart);
router.events.off('routeChangeComplete', handleComplete);
router.events.off('routeChangeError', handleComplete);
};
});

return (
loading && (
<div className="spinner-wrapper">
<div className="spinner"></div>
</div>
)
);
};

function MyApp({ Component, pageProps: { session, ...pageProps } }) {
const [queryClient] = useState(() => new QueryClient());

@@ -59,7 +28,7 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }) {
content="width=device-width, initial-scale=1"
/>
</Head>
<LoadingSpinner />
<CircularIndeterminate />
<Component {...pageProps} />
</Layout>
</SessionProvider>

+ 0
- 63
styles/globals.css 查看文件

@@ -17,66 +17,3 @@ h5,
h6 {
font-family: 'Lato', sans-serif;
}

.spinner-wrapper {
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
position: fixed;
top: 0;
left: 0;
background-color: rgb(255, 255, 255);
z-index: 99;
}
.spinner {
position: absolute;
left: 50%;
top: 50%;
height: 60px;
width: 60px;
margin: 0px auto;
-webkit-animation: rotation 0.6s infinite linear;
-moz-animation: rotation 0.6s infinite linear;
-o-animation: rotation 0.6s infinite linear;
animation: rotation 0.6s infinite linear;
border-left: 6px solid rgba(0, 174, 239, 0.15);
border-right: 6px solid rgba(0, 174, 239, 0.15);
border-bottom: 6px solid rgba(0, 174, 239, 0.15);
border-top: 6px solid rgba(0, 174, 239, 0.8);
border-radius: 100%;
}

@-webkit-keyframes rotation {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(359deg);
}
}
@-moz-keyframes rotation {
from {
-moz-transform: rotate(0deg);
}
to {
-moz-transform: rotate(359deg);
}
}
@-o-keyframes rotation {
from {
-o-transform: rotate(0deg);
}
to {
-o-transform: rotate(359deg);
}
}
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}

Loading…
取消
儲存