Compare commits
3 Commits
ceeda1a771
...
0ab6aaac1c
Author | SHA1 | Date | |
---|---|---|---|
0ab6aaac1c
|
|||
89b1f10b6e
|
|||
570a969cee
|
@@ -38,3 +38,6 @@ target = "x86_64-unknown-linux-gnu"
|
|||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
regex = "1.10.3"
|
regex = "1.10.3"
|
||||||
|
|
||||||
|
[package.metadata.turf.class_names]
|
||||||
|
template = "<original_name>--<id>"
|
||||||
|
@@ -69,16 +69,14 @@ $border-radius: 16px;
|
|||||||
|
|
||||||
$geist-font-path: "../fonts/Geist";
|
$geist-font-path: "../fonts/Geist";
|
||||||
|
|
||||||
|
$transition-duration: 300ms;
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
src: url("#{$geist-font-path}/Geist-Bold.woff2") format("woff2");
|
src: url("#{$geist-font-path}/Geist-Bold.woff2") format("woff2");
|
||||||
font-family: "Geist";
|
font-family: "Geist";
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
|
||||||
font-family: "Geist";
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
@@ -90,8 +88,15 @@ body {
|
|||||||
#main {
|
#main {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
font-family: "Geist";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: To remove once the design updated.
|
// TODO: To remove once the design updated.
|
||||||
.aeroButton {
|
.aeroButton {
|
||||||
height: 50%;
|
height: 50%;
|
||||||
|
@@ -7,7 +7,7 @@ pub fn AvatarSelector(cx: Scope) -> Element {
|
|||||||
style { STYLE_SHEET },
|
style { STYLE_SHEET },
|
||||||
|
|
||||||
div {
|
div {
|
||||||
class: ClassName::SELECTOR,
|
class: ClassName::AVATAR_SELECTOR,
|
||||||
svg {
|
svg {
|
||||||
view_box: "0 0 100 100",
|
view_box: "0 0 100 100",
|
||||||
linearGradient {
|
linearGradient {
|
||||||
@@ -46,7 +46,7 @@ pub fn AvatarSelector(cx: Scope) -> Element {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
img {
|
img {
|
||||||
class: ClassName::PICTURE,
|
class: ClassName::AVATAR_SELECTOR_PICTURE,
|
||||||
src: "./images/default-avatar.png",
|
src: "./images/default-avatar.png",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
.selector {
|
.avatar-selector {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
|
|
||||||
.picture {
|
&__picture {
|
||||||
$height: 65%;
|
$height: 65%;
|
||||||
$margin: calc(100% - $height) / 2;
|
$margin: calc(100% - $height) / 2;
|
||||||
|
|
||||||
|
@@ -80,7 +80,7 @@ pub fn RegisterButton<'a>(cx: Scope<'a, ButtonProps>) -> Element<'a> {
|
|||||||
Button {
|
Button {
|
||||||
id: cx.props.id.unwrap_or(""),
|
id: cx.props.id.unwrap_or(""),
|
||||||
|
|
||||||
style: ClassName::REGISTER,
|
style: ClassName::REGISTER_BUTTON,
|
||||||
|
|
||||||
onclick: |event| {
|
onclick: |event| {
|
||||||
if let Some(cb) = &cx.props.onclick {
|
if let Some(cb) = &cx.props.onclick {
|
||||||
@@ -126,7 +126,7 @@ pub fn LoginButton<'a>(cx: Scope<'a, ButtonProps>) -> Element<'a> {
|
|||||||
Button {
|
Button {
|
||||||
id: cx.props.id.unwrap_or(""),
|
id: cx.props.id.unwrap_or(""),
|
||||||
|
|
||||||
style: ClassName::LOGIN,
|
style: ClassName::LOGIN_BUTTON,
|
||||||
|
|
||||||
onclick: |event| {
|
onclick: |event| {
|
||||||
if let Some(cb) = &cx.props.onclick {
|
if let Some(cb) = &cx.props.onclick {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
@import "../_base.scss"
|
@import "../_base.scss"
|
||||||
|
|
||||||
.root {
|
%button {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
aspect-ratio: 3.5;
|
aspect-ratio: 3.5;
|
||||||
|
|
||||||
@@ -18,27 +18,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.register {
|
.register-button {
|
||||||
@extend .root;
|
@extend %button;
|
||||||
|
|
||||||
background-color: $color-ternary-90;
|
background-color: $color-ternary-90;
|
||||||
}
|
|
||||||
.register:hover {
|
&:hover {
|
||||||
background-color: $color-ternary-80;
|
background-color: $color-ternary-80;
|
||||||
}
|
}
|
||||||
.register:active {
|
|
||||||
background-color: $color-ternary-70;
|
&:active {
|
||||||
|
background-color: $color-ternary-70;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.login {
|
.login-button {
|
||||||
@extend .root;
|
@extend %button;
|
||||||
|
|
||||||
background-color: $color-secondary-90;
|
background-color: $color-secondary-90;
|
||||||
}
|
|
||||||
.login:hover {
|
&:hover {
|
||||||
background-color: $color-secondary-80;
|
background-color: $color-secondary-80;
|
||||||
}
|
}
|
||||||
.login:active {
|
|
||||||
background-color: $color-secondary-70;
|
&:active {
|
||||||
|
background-color: $color-secondary-70;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,13 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_free_icons::icons::md_navigation_icons::MdArrowDropDown;
|
use dioxus_free_icons::icons::md_navigation_icons::MdArrowDropDown;
|
||||||
use dioxus_free_icons::Icon;
|
use dioxus_free_icons::{Icon, IconShape};
|
||||||
|
|
||||||
turf::style_sheet!("src/components/icons.scss");
|
turf::style_sheet!("src/components/icons.scss");
|
||||||
|
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/style_vars.rs"));
|
||||||
|
|
||||||
|
use style::{COLOR_PRIMARY_100, COLOR_TERNARY_100};
|
||||||
|
|
||||||
pub fn DownArrowIcon(cx: Scope) -> Element {
|
pub fn DownArrowIcon(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
style { STYLE_SHEET },
|
style { STYLE_SHEET },
|
||||||
@@ -14,3 +18,113 @@ pub fn DownArrowIcon(cx: Scope) -> Element {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _PYRAMID_OFFSET_X: f64 = 1.0;
|
||||||
|
const _PYRAMID_OFFSET_Y: f64 = 2.0;
|
||||||
|
const _PYRAMID_STROKE_WIDTH: f64 = 2.0;
|
||||||
|
|
||||||
|
const _PYRAMID_DIST_FROM_CENTRAL_X: f64 = 65.0;
|
||||||
|
const _PYRAMID_DIST_FROM_CENTRAL_E1_Y: f64 = 83.0;
|
||||||
|
|
||||||
|
const _PYRAMID_EDGES_E1_X: f64 = _PYRAMID_DIST_FROM_CENTRAL_X + _PYRAMID_OFFSET_X;
|
||||||
|
const _PYRAMID_EDGES_E1_Y: f64 = _PYRAMID_OFFSET_Y;
|
||||||
|
|
||||||
|
const _PYRAMID_LEFT_EDGE_E2_X: f64 = _PYRAMID_OFFSET_X;
|
||||||
|
const _PYRAMID_LEFT_EDGE_E2_Y: f64 = _PYRAMID_DIST_FROM_CENTRAL_E1_Y + _PYRAMID_OFFSET_Y;
|
||||||
|
|
||||||
|
const _PYRAMID_CENTRAL_EDGE_E2_X: f64 = _PYRAMID_DIST_FROM_CENTRAL_X + _PYRAMID_OFFSET_X;
|
||||||
|
const _PYRAMID_CENTRAL_EDGE_E2_Y: f64 = 100.0 + _PYRAMID_OFFSET_Y;
|
||||||
|
const _PYRAMID_CENTRAL_EDGE_Y_LEN: f64 = _PYRAMID_CENTRAL_EDGE_E2_Y - _PYRAMID_EDGES_E1_Y;
|
||||||
|
|
||||||
|
const _PYRAMID_RIGHT_EDGE_E2_X: f64 = 130.0 + _PYRAMID_OFFSET_X;
|
||||||
|
const _PYRAMID_RIGHT_EDGE_E2_Y: f64 = _PYRAMID_LEFT_EDGE_E2_Y;
|
||||||
|
|
||||||
|
struct PyramidShape<'a> {
|
||||||
|
color: &'a str,
|
||||||
|
ratio: f64,
|
||||||
|
progress_color: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IconShape for PyramidShape<'a> {
|
||||||
|
fn view_box(&self) -> String {
|
||||||
|
let height = _PYRAMID_CENTRAL_EDGE_E2_Y + _PYRAMID_STROKE_WIDTH;
|
||||||
|
let width = _PYRAMID_RIGHT_EDGE_E2_X + _PYRAMID_STROKE_WIDTH;
|
||||||
|
format!("0 0 {width} {height}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn xmlns(&self) -> String {
|
||||||
|
String::from("http://www.w3.org/2000/svg")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn child_elements(&self) -> LazyNodes {
|
||||||
|
let inverted_ratio = 1.0 - self.ratio;
|
||||||
|
|
||||||
|
let central_edge_ratio_e2_y =
|
||||||
|
_PYRAMID_CENTRAL_EDGE_Y_LEN * inverted_ratio + _PYRAMID_OFFSET_Y;
|
||||||
|
|
||||||
|
let left_edge_ratio_e1_x = _PYRAMID_OFFSET_X + (_PYRAMID_DIST_FROM_CENTRAL_X * self.ratio);
|
||||||
|
let right_edge_ratio_e1_x = _PYRAMID_OFFSET_X
|
||||||
|
+ _PYRAMID_EDGES_E1_X
|
||||||
|
+ (_PYRAMID_DIST_FROM_CENTRAL_X * inverted_ratio);
|
||||||
|
let no_central_edge_ratio_e1_y =
|
||||||
|
_PYRAMID_OFFSET_Y + (_PYRAMID_DIST_FROM_CENTRAL_E1_Y * inverted_ratio);
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
g {
|
||||||
|
stroke: "#fff",
|
||||||
|
"stroke-linejoin": "round",
|
||||||
|
"stroke-width": _PYRAMID_STROKE_WIDTH,
|
||||||
|
fill: "#{self.progress_color}",
|
||||||
|
|
||||||
|
path {
|
||||||
|
fill: "#{self.color}",
|
||||||
|
d: "\
|
||||||
|
M {_PYRAMID_EDGES_E1_X} {_PYRAMID_EDGES_E1_Y} \
|
||||||
|
L {_PYRAMID_RIGHT_EDGE_E2_X} {_PYRAMID_RIGHT_EDGE_E2_Y} \
|
||||||
|
L {_PYRAMID_EDGES_E1_X} {_PYRAMID_CENTRAL_EDGE_E2_Y} \
|
||||||
|
M {_PYRAMID_EDGES_E1_X} {_PYRAMID_EDGES_E1_Y} \
|
||||||
|
L {_PYRAMID_LEFT_EDGE_E2_X} {_PYRAMID_LEFT_EDGE_E2_Y} \
|
||||||
|
L {_PYRAMID_EDGES_E1_X} {_PYRAMID_CENTRAL_EDGE_E2_Y} \
|
||||||
|
M {_PYRAMID_EDGES_E1_X} {_PYRAMID_EDGES_E1_Y} \
|
||||||
|
V {_PYRAMID_CENTRAL_EDGE_Y_LEN}",
|
||||||
|
},
|
||||||
|
path {
|
||||||
|
d: "\
|
||||||
|
M {_PYRAMID_CENTRAL_EDGE_E2_X} {_PYRAMID_CENTRAL_EDGE_E2_Y} \
|
||||||
|
V {central_edge_ratio_e2_y} \
|
||||||
|
L {left_edge_ratio_e1_x} {no_central_edge_ratio_e1_y} \
|
||||||
|
L {_PYRAMID_LEFT_EDGE_E2_X} {_PYRAMID_LEFT_EDGE_E2_Y} Z",
|
||||||
|
},
|
||||||
|
path {
|
||||||
|
d: "\
|
||||||
|
M {_PYRAMID_CENTRAL_EDGE_E2_X} {_PYRAMID_CENTRAL_EDGE_E2_Y} \
|
||||||
|
V {central_edge_ratio_e2_y} \
|
||||||
|
L {right_edge_ratio_e1_x} {no_central_edge_ratio_e1_y} \
|
||||||
|
L {_PYRAMID_RIGHT_EDGE_E2_X} {_PYRAMID_RIGHT_EDGE_E2_Y} Z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Props)]
|
||||||
|
pub struct PyramidProps<'a> {
|
||||||
|
#[props(default = 0.5)]
|
||||||
|
color: Option<&'a str>,
|
||||||
|
ratio: f64,
|
||||||
|
progress_color: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Pyramid<'a>(cx: Scope<'a, PyramidProps<'a>>) -> Element<'a> {
|
||||||
|
let progress_color = cx.props.progress_color.unwrap_or(COLOR_PRIMARY_100);
|
||||||
|
let color = cx.props.color.unwrap_or(COLOR_TERNARY_100);
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
style { STYLE_SHEET },
|
||||||
|
|
||||||
|
Icon {
|
||||||
|
class: ClassName::PYRAMID_ICON,
|
||||||
|
icon: PyramidShape { ratio: cx.props.ratio, color: color, progress_color: progress_color },
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -5,3 +5,8 @@
|
|||||||
fill: white;
|
fill: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pyramid-icon {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
@@ -14,12 +14,12 @@ pub fn LoadingPage(cx: Scope) -> Element {
|
|||||||
style { STYLE_SHEET },
|
style { STYLE_SHEET },
|
||||||
|
|
||||||
div {
|
div {
|
||||||
class: ClassName::ROOT,
|
class: ClassName::LOADING,
|
||||||
|
|
||||||
Wallpaper {},
|
Wallpaper {},
|
||||||
|
|
||||||
div {
|
div {
|
||||||
class: ClassName::SPINNER,
|
class: ClassName::LOADING_SPINNER,
|
||||||
Spinner {},
|
Spinner {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
@import "../_base.scss"
|
@import "../_base.scss"
|
||||||
@import "./spinner.scss"
|
@import "./spinner.scss"
|
||||||
|
|
||||||
.root {
|
.loading {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
.spinner {
|
&__spinner {
|
||||||
height: 5%;
|
height: 5%;
|
||||||
aspect-ratio: $logo-aspect-ratio;
|
aspect-ratio: $logo-aspect-ratio;
|
||||||
|
|
||||||
@@ -77,5 +77,4 @@
|
|||||||
|
|
||||||
background-color: $greyscale-0;
|
background-color: $greyscale-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -375,14 +375,14 @@ pub fn Login<'a>(cx: Scope<'a, LoginProps>) -> Element<'a> {
|
|||||||
let avatar = match random_avatar_future.value() {
|
let avatar = match random_avatar_future.value() {
|
||||||
Some(Some(svg)) => {
|
Some(Some(svg)) => {
|
||||||
rsx!(div {
|
rsx!(div {
|
||||||
class: ClassName::CONTENT,
|
class: ClassName::LOGIN_FORM_PHOTO_CONTENT,
|
||||||
dangerous_inner_html: svg.as_str(),
|
dangerous_inner_html: svg.as_str(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Some(None) | None => {
|
Some(None) | None => {
|
||||||
warn!("No profile image set or generated, display the placeholder");
|
warn!("No profile image set or generated, display the placeholder");
|
||||||
rsx!(div {
|
rsx!(div {
|
||||||
class: ClassName::CONTENT,
|
class: ClassName::LOGIN_FORM_PHOTO_CONTENT,
|
||||||
img {
|
img {
|
||||||
src: "./images/login-profile-placeholder.svg"
|
src: "./images/login-profile-placeholder.svg"
|
||||||
}
|
}
|
||||||
@@ -468,13 +468,13 @@ pub fn Login<'a>(cx: Scope<'a, LoginProps>) -> Element<'a> {
|
|||||||
Wallpaper {},
|
Wallpaper {},
|
||||||
|
|
||||||
div {
|
div {
|
||||||
class: ClassName::ROOT,
|
class: ClassName::LOGIN,
|
||||||
|
|
||||||
div {
|
div {
|
||||||
class: "{form_classes_str}",
|
class: "{form_classes_str}",
|
||||||
|
|
||||||
div {
|
div {
|
||||||
class: ClassName::PHOTO,
|
class: ClassName::LOGIN_FORM_PHOTO,
|
||||||
|
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
random_avatar_future.restart()
|
random_avatar_future.restart()
|
||||||
@@ -484,7 +484,7 @@ pub fn Login<'a>(cx: Scope<'a, LoginProps>) -> Element<'a> {
|
|||||||
},
|
},
|
||||||
|
|
||||||
div {
|
div {
|
||||||
class: ClassName::HOMESERVER,
|
class: ClassName::LOGIN_FORM_HOMESERVER,
|
||||||
TextInput {
|
TextInput {
|
||||||
id: "hs_url",
|
id: "hs_url",
|
||||||
r#type: "text",
|
r#type: "text",
|
||||||
@@ -497,7 +497,7 @@ pub fn Login<'a>(cx: Scope<'a, LoginProps>) -> Element<'a> {
|
|||||||
},
|
},
|
||||||
|
|
||||||
div {
|
div {
|
||||||
class: ClassName::ID,
|
class: ClassName::LOGIN_FORM_ID,
|
||||||
TextInput {
|
TextInput {
|
||||||
r#type: "text",
|
r#type: "text",
|
||||||
placeholder: "{id_placeholder}",
|
placeholder: "{id_placeholder}",
|
||||||
@@ -533,21 +533,21 @@ pub fn Login<'a>(cx: Scope<'a, LoginProps>) -> Element<'a> {
|
|||||||
},
|
},
|
||||||
|
|
||||||
div {
|
div {
|
||||||
class: ClassName::SPINNER,
|
class: ClassName::LOGIN_FORM_SPINNER,
|
||||||
Spinner {
|
Spinner {
|
||||||
animate: **spinner_animated,
|
animate: **spinner_animated,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
div {
|
div {
|
||||||
class: ClassName::REGISTER,
|
class: ClassName::LOGIN_FORM_REGISTER_BUTTON,
|
||||||
RegisterButton {
|
RegisterButton {
|
||||||
onclick: on_register,
|
onclick: on_register,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
div {
|
div {
|
||||||
class: ClassName::LOGIN,
|
class: ClassName::LOGIN_FORM_LOGIN_BUTTON,
|
||||||
LoginButton {
|
LoginButton {
|
||||||
focus: true,
|
focus: true,
|
||||||
onclick: on_login,
|
onclick: on_login,
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
@import "../_base.scss"
|
@import "../_base.scss"
|
||||||
@import "./spinner.scss"
|
@import "./spinner.scss"
|
||||||
|
|
||||||
.root {
|
.login {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@@ -12,7 +12,9 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
top: -100vh;
|
top: -100vh;
|
||||||
|
|
||||||
.form {
|
margin-bottom: -100vh;
|
||||||
|
|
||||||
|
&__form {
|
||||||
$height: 95%;
|
$height: 95%;
|
||||||
height: $height;
|
height: $height;
|
||||||
|
|
||||||
@@ -64,13 +66,13 @@
|
|||||||
". . . . . . . . . . ."
|
". . . . . . . . . . ."
|
||||||
;
|
;
|
||||||
|
|
||||||
transition: 300ms;
|
transition: $transition-duration;
|
||||||
|
|
||||||
&.register {
|
&.register {
|
||||||
grid-template-rows: $padding-row $profile-img-height auto 5% 5% 5% 5% 5% 5% 5% 5% $spinner-height 5% $button-height $padding-row;
|
grid-template-rows: $padding-row $profile-img-height auto 5% 5% 5% 5% 5% 5% 5% 5% $spinner-height 5% $button-height $padding-row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.photo {
|
&__photo {
|
||||||
grid-area: photo;
|
grid-area: photo;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -82,25 +84,25 @@
|
|||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.content {
|
&__content {
|
||||||
height: calc(100% + (2 * $border-big-width));
|
height: calc(100% + (2 * $border-big-width));
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.homeserver {
|
&__homeserver {
|
||||||
grid-area: homeserver;
|
grid-area: homeserver;
|
||||||
}
|
}
|
||||||
|
|
||||||
.id {
|
&__id {
|
||||||
grid-area: id;
|
grid-area: id;
|
||||||
}
|
}
|
||||||
|
|
||||||
.password {
|
&__password {
|
||||||
grid-area: password;
|
grid-area: password;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-password {
|
&__confirm-password {
|
||||||
grid-area: confirm;
|
grid-area: confirm;
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
@@ -109,7 +111,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.spinner {
|
&__spinner {
|
||||||
grid-area: spinner;
|
grid-area: spinner;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,11 +119,11 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.register {
|
&__register-button {
|
||||||
grid-area: register;
|
grid-area: register;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login {
|
&__login-button {
|
||||||
grid-area: login;
|
grid-area: login;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -34,7 +34,7 @@ pub fn Spinner(cx: Scope<SpinnerProps>) -> Element {
|
|||||||
style { STYLE_SHEET },
|
style { STYLE_SHEET },
|
||||||
|
|
||||||
div {
|
div {
|
||||||
class: ClassName::ROOT,
|
class: ClassName::SPINNER,
|
||||||
|
|
||||||
Icon {
|
Icon {
|
||||||
class: if cx.props.animate { "" } else { ClassName::PAUSED },
|
class: if cx.props.animate { "" } else { ClassName::PAUSED },
|
||||||
|
@@ -6,7 +6,7 @@ $logo-height: calc(32px * 2);
|
|||||||
$logo-width: calc(64px * 2);
|
$logo-width: calc(64px * 2);
|
||||||
$logo-aspect-ratio: calc($logo-width / $logo-height);
|
$logo-aspect-ratio: calc($logo-width / $logo-height);
|
||||||
|
|
||||||
.root {
|
.spinner {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
@@ -1,8 +1,23 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_free_icons::icons::io_icons::IoEye;
|
||||||
|
use dioxus_free_icons::icons::io_icons::IoEyeOff;
|
||||||
|
use dioxus_free_icons::Icon;
|
||||||
|
|
||||||
|
use super::icons::Pyramid;
|
||||||
|
|
||||||
turf::style_sheet!("src/components/text_input.scss");
|
turf::style_sheet!("src/components/text_input.scss");
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub trait InputPropsData {}
|
||||||
|
|
||||||
|
#[derive(Props)]
|
||||||
|
pub struct InputProps<'a, D: InputPropsData + 'a> {
|
||||||
|
value: Option<&'a str>,
|
||||||
|
placeholder: Option<&'a str>,
|
||||||
|
oninput: Option<EventHandler<'a, Event<FormData>>>,
|
||||||
|
state: Option<&'a UseRef<D>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub struct TextInputState {
|
pub struct TextInputState {
|
||||||
pub is_valid: bool,
|
pub is_valid: bool,
|
||||||
pub helper_text: Option<String>,
|
pub helper_text: Option<String>,
|
||||||
@@ -27,62 +42,176 @@ impl TextInputState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Props)]
|
impl InputPropsData for TextInputState {}
|
||||||
pub struct TextInputProps<'a> {
|
|
||||||
id: Option<&'a str>,
|
|
||||||
r#type: Option<&'a str>,
|
|
||||||
value: Option<&'a str>,
|
|
||||||
placeholder: Option<&'a str>,
|
|
||||||
oninput: Option<EventHandler<'a, Event<FormData>>>,
|
|
||||||
state: Option<&'a UseRef<TextInputState>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn TextInput<'a>(cx: Scope<'a, TextInputProps<'a>>) -> Element<'a> {
|
pub fn TextInput<'a>(cx: Scope<'a, InputProps<'a, TextInputState>>) -> Element<'a> {
|
||||||
let mut level_class = "";
|
let mut criticity_class = "";
|
||||||
let mut helper_text: String = "".to_string();
|
let mut helper_text = "".to_string();
|
||||||
|
|
||||||
match cx.props.state {
|
match cx.props.state {
|
||||||
Some(state) => {
|
Some(state) => {
|
||||||
if !state.read().is_valid {
|
let state = state.read();
|
||||||
level_class = ClassName::INVALID;
|
if !state.is_valid {
|
||||||
|
criticity_class = ClassName::INVALID;
|
||||||
}
|
}
|
||||||
if let Some(text) = &state.read().helper_text {
|
if let Some(text) = &state.helper_text {
|
||||||
helper_text = text.to_string();
|
helper_text = text.to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let input_classes_str = [ClassName::TEXT_INPUT_INPUT, criticity_class].join(" ");
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
style { STYLE_SHEET },
|
style { STYLE_SHEET },
|
||||||
|
|
||||||
div {
|
div {
|
||||||
class: ClassName::ROOT,
|
class: ClassName::TEXT_INPUT,
|
||||||
|
|
||||||
input {
|
input {
|
||||||
class: level_class,
|
class: "{input_classes_str}",
|
||||||
|
r#type: "text",
|
||||||
id: cx.props.id.unwrap_or(""),
|
placeholder: cx.props.placeholder,
|
||||||
|
value: cx.props.value,
|
||||||
|
|
||||||
oninput: move |evt| {
|
oninput: move |evt| {
|
||||||
if let Some(cb) = &cx.props.oninput {
|
if let Some(cb) = &cx.props.oninput {
|
||||||
cb.call(evt);
|
cb.call(evt);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
r#type: cx.props.r#type,
|
|
||||||
placeholder: cx.props.placeholder,
|
|
||||||
value: cx.props.value,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
div {
|
div {
|
||||||
class: ClassName::HELPER_TEXT,
|
class: ClassName::TEXT_INPUT_HELPER_TEXT,
|
||||||
|
|
||||||
p {
|
p {
|
||||||
class: level_class,
|
class: criticity_class,
|
||||||
|
|
||||||
helper_text
|
helper_text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Props)]
|
||||||
|
pub struct PasswordInputState {
|
||||||
|
text_input_state: TextInputState,
|
||||||
|
#[props(default = 0.0)]
|
||||||
|
pub score: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PasswordInputState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
PasswordInputState {
|
||||||
|
text_input_state: TextInputState::new(),
|
||||||
|
score: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.text_input_state.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invalidate(&mut self, helper_text: String) {
|
||||||
|
self.text_input_state.invalidate(helper_text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputPropsData for PasswordInputState {}
|
||||||
|
|
||||||
|
pub fn PasswordTextInput<'a>(cx: Scope<'a, InputProps<'a, PasswordInputState>>) -> Element<'a> {
|
||||||
|
let mut criticity_class = "";
|
||||||
|
let mut helper_text: String = "".to_string();
|
||||||
|
let mut score: Option<f64> = None;
|
||||||
|
|
||||||
|
let show_password = use_state(cx, || false);
|
||||||
|
|
||||||
|
match cx.props.state {
|
||||||
|
Some(state) => {
|
||||||
|
let state = state.read();
|
||||||
|
if !state.text_input_state.is_valid {
|
||||||
|
criticity_class = ClassName::INVALID;
|
||||||
|
}
|
||||||
|
if let Some(text) = &state.text_input_state.helper_text {
|
||||||
|
helper_text = text.to_string();
|
||||||
|
}
|
||||||
|
score = Some(state.score);
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let text_input_classes = [
|
||||||
|
ClassName::PASSWORD_TEXT_INPUT,
|
||||||
|
if score.is_none() {
|
||||||
|
ClassName::NO_STRENGTH
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.join(" ");
|
||||||
|
let input_classes = [ClassName::PASSWORD_TEXT_INPUT_INPUT, criticity_class].join(" ");
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
style { STYLE_SHEET },
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "{text_input_classes}",
|
||||||
|
|
||||||
|
input {
|
||||||
|
class: "{input_classes}",
|
||||||
|
r#type: if **show_password { "text" } else { "password" },
|
||||||
|
placeholder: cx.props.placeholder,
|
||||||
|
value: cx.props.value,
|
||||||
|
|
||||||
|
oninput: move |evt| {
|
||||||
|
if let Some(cb) = &cx.props.oninput {
|
||||||
|
cb.call(evt);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
if let Some(score) = score {
|
||||||
|
rsx!(
|
||||||
|
div {
|
||||||
|
class: ClassName::PASSWORD_TEXT_INPUT_STRENGTH_LEVEL,
|
||||||
|
Pyramid {
|
||||||
|
ratio: score,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::PASSWORD_TEXT_INPUT_SHOW_TOGGLE,
|
||||||
|
onclick: move |_| {
|
||||||
|
show_password.set(!**show_password);
|
||||||
|
},
|
||||||
|
|
||||||
|
if **show_password {
|
||||||
|
rsx!(
|
||||||
|
Icon {
|
||||||
|
icon: IoEyeOff,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rsx!(
|
||||||
|
Icon {
|
||||||
|
icon: IoEye,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::PASSWORD_TEXT_INPUT_HELPER_TEXT,
|
||||||
|
|
||||||
|
p {
|
||||||
|
class: criticity_class,
|
||||||
|
helper_text
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -1,47 +1,130 @@
|
|||||||
@import "../_base.scss"
|
@import "../_base.scss"
|
||||||
|
|
||||||
.root {
|
%base-text-input {
|
||||||
|
$horizontal-padding: 1vw;
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: calc(100% - (2 * $horizontal-padding));
|
||||||
|
|
||||||
input {
|
border: $border-normal;
|
||||||
$horizontal-padding: 1vw;
|
border-color: $color-primary-90;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
|
||||||
padding-left: $horizontal-padding;
|
padding-left: $horizontal-padding;
|
||||||
padding-right: $horizontal-padding;
|
padding-right: $horizontal-padding;
|
||||||
padding-top: 0px;
|
padding-top: 0px;
|
||||||
padding-bottom: 0px;
|
padding-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
height: calc(100% - (2 * ($border-normal-width)));
|
%base-input {
|
||||||
width: calc(100% - (2 * ($border-normal-width + $horizontal-padding)));
|
$horizontal-padding: 1vw;
|
||||||
|
|
||||||
margin: 0;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
border: $border-normal;
|
margin: 0;
|
||||||
border-color: $color-primary-90;
|
padding: 0;
|
||||||
border-radius: $border-radius;
|
border: 0;
|
||||||
|
|
||||||
font-size: 2vh;
|
font-size: 2vh;
|
||||||
|
|
||||||
&.invalid {
|
&:focus {
|
||||||
border-color: $color-critical;
|
outline: none;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.helper-text {
|
&.invalid {
|
||||||
|
border-color: $color-critical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
%base-helper-text {
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 0.3vh;
|
||||||
|
|
||||||
|
font-size: 1.2vh;
|
||||||
|
|
||||||
|
color: $color-primary-90;
|
||||||
|
|
||||||
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-top: 0.3vh;
|
|
||||||
|
|
||||||
font-size: 1.2vh;
|
&.invalid {
|
||||||
|
color: $color-critical;
|
||||||
color: $color-primary-90;
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 1vw;
|
|
||||||
|
|
||||||
&.invalid {
|
|
||||||
color: $color-critical;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-input {
|
||||||
|
@extend %base-text-input;
|
||||||
|
|
||||||
|
&__input {
|
||||||
|
@extend %base-input;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__helper-text {
|
||||||
|
@extend %base-helper-text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-text-input {
|
||||||
|
@extend %base-text-input;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 7.5% 1% 7.5%;
|
||||||
|
grid-template-rows: 100%;
|
||||||
|
grid-template-areas:
|
||||||
|
"input strength . toggle"
|
||||||
|
"helper helper helper helper"
|
||||||
|
;
|
||||||
|
|
||||||
|
transition: $transition-duration;
|
||||||
|
|
||||||
|
&.no-strength {
|
||||||
|
grid-template-columns: auto 0% 0% 7.5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__input {
|
||||||
|
@extend %base-input;
|
||||||
|
|
||||||
|
grid-area: input;
|
||||||
|
}
|
||||||
|
|
||||||
|
%inner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__strength-level {
|
||||||
|
@extend %inner;
|
||||||
|
|
||||||
|
grid-area: strength;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
font-size: 2vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__show-toggle {
|
||||||
|
@extend %inner;
|
||||||
|
|
||||||
|
grid-area: toggle;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
color: $color-secondary-100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__helper-text {
|
||||||
|
@extend %base-helper-text;
|
||||||
|
|
||||||
|
grid-area: helper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -7,10 +7,10 @@ pub fn Wallpaper(cx: Scope) -> Element {
|
|||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
style { STYLE_SHEET },
|
style { STYLE_SHEET },
|
||||||
div {
|
div {
|
||||||
class: ClassName::ROOT,
|
class: ClassName::WALLPAPER,
|
||||||
|
|
||||||
div {
|
div {
|
||||||
class: ClassName::CONTENT,
|
class: ClassName::WALLPAPER_CONTENT,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
@import "../_base.scss"
|
@import "../_base.scss"
|
||||||
|
|
||||||
.root {
|
.wallpaper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.content {
|
&__content {
|
||||||
background-image: url("./images/wallpaper-pattern.svg");
|
background-image: url("./images/wallpaper-pattern.svg");
|
||||||
background-position: center;
|
background-position: center;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user