Draw confirm delete issue, emit valid msg on confirm

This commit is contained in:
Adrian Wozniak 2020-04-03 23:43:29 +02:00
parent f2b5753f83
commit 135907384e
28 changed files with 372 additions and 247 deletions

View File

@ -77,6 +77,29 @@
color: var(--primary);
}
.styledModal.confirmModal {
.modal > .clickableOverlay > .styledModal.confirmModal {
padding: 35px 40px 40px;
}
.modal > .clickableOverlay > .styledModal.confirmModal > .title {
padding-bottom: 25px;
font-family: var(--font-medium);
font-weight: normal;
font-size: 22px;
line-height: 1.5;
}
.modal > .clickableOverlay > .styledModal.confirmModal > .message {
padding-bottom: 25px;
white-space: pre-wrap;
font-size: 15px
}
.modal > .clickableOverlay > .styledModal.confirmModal > .actions {
display: flex;
padding-top: 6px;
}
.modal > .clickableOverlay > .styledModal.confirmModal > .actions > .styledButton {
margin-right: 10px;
}

View File

@ -56,6 +56,7 @@ pub enum Msg {
// issues
IssueUpdateResult(FetchObject<String>),
DeleteIssue(IssueId),
// modals
ModalOpened(ModalType),

View File

@ -1,12 +1,28 @@
use seed::{prelude::*, *};
use crate::shared::styled_modal::StyledModal;
use crate::model::ModalType;
use crate::shared::styled_confirm_modal::StyledConfirmModal;
use crate::shared::ToNode;
use crate::{model, Msg};
pub fn view(model: &model::Model) -> Node<Msg> {
let handle_issue_delete = mouse_ev(Ev::Click, |_| Msg::NoOp);
StyledModal::build()
let opt_id = model
.modals
.iter()
.filter_map(|modal| match modal {
ModalType::EditIssue(issue_id, _) => Some(issue_id),
_ => None,
})
.find(|id| id.eq(id));
let issue_id = match opt_id {
Some(id) => *id,
_ => return empty![],
};
let handle_issue_delete = mouse_ev(Ev::Click, move |_| Msg::DeleteIssue(issue_id));
StyledConfirmModal::build()
.title("Are you sure you want to delete this issue?")
.message("Once you delete, it's gone for good.")
.confirm_text("Delete issue")

View File

@ -4,7 +4,7 @@ use jirs_data::{Issue, IssueType, UpdateIssuePayload};
use crate::api::update_issue;
use crate::model::{EditIssueModal, ModalType, Page};
use crate::shared::modal::{Modal, Variant as ModalVariant};
use crate::shared::styled_modal::{StyledModal, Variant as ModalVariant};
use crate::shared::styled_select::StyledSelectChange;
use crate::shared::{find_issue, ToNode};
use crate::{model, FieldChange, FieldId, Msg};
@ -114,14 +114,12 @@ pub fn view(model: &model::Model) -> Node<Msg> {
ModalType::EditIssue(issue_id, modal) => {
if let Some(issue) = find_issue(model, *issue_id) {
let details = issue_details::view(model, issue, &modal);
let modal = Modal {
variant: ModalVariant::Center,
width: 1040,
with_icon: false,
children: vec![details],
}
.into_node();
modal
StyledModal::build()
.variant(ModalVariant::Center)
.width(1040)
.children(vec![details])
.build()
.into_node()
} else {
empty![]
}

View File

@ -7,10 +7,10 @@ use crate::model::Model;
use crate::{IssueId, Msg};
pub mod aside;
pub mod modal;
pub mod navbar_left;
pub mod styled_avatar;
pub mod styled_button;
pub mod styled_confirm_modal;
pub mod styled_icon;
pub mod styled_input;
pub mod styled_modal;

View File

@ -1,83 +0,0 @@
use seed::{prelude::*, *};
use crate::shared::styled_icon::{Icon, StyledIcon};
use crate::shared::ToNode;
use crate::Msg;
#[allow(dead_code)]
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
pub enum Variant {
Center,
Aside,
}
impl Variant {
pub fn to_class_name(&self) -> &str {
match self {
Variant::Center => "center",
Variant::Aside => "aside",
}
}
pub fn to_icon_class_name(&self) -> &str {
match self {
Variant::Center => "modalVariantCenter",
Variant::Aside => "modalVariantAside",
}
}
}
#[derive(Debug)]
pub struct Modal {
pub variant: Variant,
pub width: usize,
pub with_icon: bool,
pub children: Vec<Node<Msg>>,
}
impl ToNode for Modal {
fn into_node(self) -> Node<Msg> {
render(self)
}
}
pub fn render(values: Modal) -> Node<Msg> {
let Modal {
variant,
width,
with_icon,
children,
} = values;
let icon = if with_icon {
StyledIcon::build(Icon::Close)
.add_class(variant.to_icon_class_name().to_string())
.build()
.into_node()
} else {
empty![]
};
let close_handler = mouse_ev(Ev::Click, |_| Msg::ModalDropped);
let body_handler = mouse_ev(Ev::Click, |ev| {
ev.stop_propagation();
Msg::NoOp
});
let clickable_class = format!("clickableOverlay {}", variant.to_class_name());
let styled_modal_class = format!("styledModal {}", variant.to_class_name());
let styled_modal_style = format!("max-width: {width}px", width = width);
div![
attrs![At::Class => "modal"],
div![
attrs![At::Class => clickable_class],
close_handler,
div![
attrs![At::Class => styled_modal_class, At::Style => styled_modal_style],
body_handler,
icon,
children
]
]
]
}

View File

@ -0,0 +1,152 @@
use seed::EventHandler;
use seed::{prelude::*, *};
use crate::shared::styled_button::{StyledButton, Variant as ButtonVariant};
use crate::shared::styled_modal::StyledModal;
use crate::shared::ToNode;
use crate::Msg;
const TITLE: &str = "Warning";
const MESSAGE: &str = "Are you sure you want to continue with this action?";
const CONFIRM_TEXT: &str = "Confirm";
const CANCEL_TEXT: &str = "Cancel";
#[derive(Debug)]
pub enum Variant {
Primary,
}
#[derive(Debug)]
pub struct StyledConfirmModal {
pub variant: Variant,
pub title: String,
pub message: String,
pub confirm_text: String,
pub cancel_text: String,
pub on_confirm: Option<EventHandler<Msg>>,
}
impl StyledConfirmModal {
pub fn build() -> StyledConfirmModalBuilder {
StyledConfirmModalBuilder::default()
}
}
impl ToNode for StyledConfirmModal {
fn into_node(self) -> Node<Msg> {
render(self)
}
}
#[derive(Default)]
pub struct StyledConfirmModalBuilder {
variant: Option<Variant>,
title: Option<String>,
message: Option<String>,
confirm_text: Option<String>,
cancel_text: Option<String>,
on_confirm: Option<Option<EventHandler<Msg>>>,
}
impl StyledConfirmModalBuilder {
pub fn variant(mut self, variant: Variant) -> Self {
self.variant = Some(variant);
self
}
pub fn title<S>(mut self, title: S) -> Self
where
S: Into<String>,
{
self.title = Some(title.into());
self
}
pub fn message<S>(mut self, message: S) -> Self
where
S: Into<String>,
{
self.message = Some(message.into());
self
}
pub fn confirm_text<S>(mut self, confirm_text: S) -> Self
where
S: Into<String>,
{
self.confirm_text = Some(confirm_text.into());
self
}
pub fn cancel_text<S>(mut self, cancel_text: S) -> Self
where
S: Into<String>,
{
self.cancel_text = Some(cancel_text.into());
self
}
pub fn on_confirm(mut self, on_confirm: EventHandler<Msg>) -> Self {
self.on_confirm = Some(Some(on_confirm));
self
}
pub fn build(self) -> StyledConfirmModal {
StyledConfirmModal {
variant: self.variant.unwrap_or_else(|| Variant::Primary),
title: self.title.unwrap_or_else(|| TITLE.to_string()),
message: self.message.unwrap_or_else(|| MESSAGE.to_string()),
confirm_text: self
.confirm_text
.unwrap_or_else(|| CONFIRM_TEXT.to_string()),
cancel_text: self.cancel_text.unwrap_or_else(|| CANCEL_TEXT.to_string()),
on_confirm: self.on_confirm.unwrap_or_default(),
}
}
}
pub fn render(values: StyledConfirmModal) -> Node<Msg> {
let StyledConfirmModal {
variant,
title,
message,
confirm_text,
cancel_text,
on_confirm,
} = values;
let message_node = match message {
_ if message.is_empty() => empty![],
_ => p![attrs![At::Class => "message"], message],
};
let confirm_button = match on_confirm {
Some(handler) => StyledButton::build()
.text(confirm_text)
.on_click(handler)
.build()
.into_node(),
_ => StyledButton::build().text(confirm_text).build().into_node(),
};
let cancel_button = StyledButton::build()
.text(cancel_text)
.variant(ButtonVariant::Secondary)
.on_click(mouse_ev(Ev::Click, |_| Msg::ModalDropped))
.build()
.into_node();
StyledModal::build()
.width(600)
.children(vec![
div![attrs![At::Class => "title"], title],
message_node,
div![
attrs![At::Class => "actions"],
confirm_button,
cancel_button
],
])
.add_class("confirmModal".to_string())
.build()
.into_node()
}

View File

@ -1,33 +1,39 @@
use seed::EventHandler;
use seed::{prelude::*, *};
use crate::shared::styled_icon::{Icon, StyledIcon};
use crate::shared::ToNode;
use crate::Msg;
const TITLE: &str = "Warning";
const MESSAGE: &str = "Are you sure you want to continue with this action?";
const CONFIRM_TEXT: &str = "Confirm";
const CANCEL_TEXT: &str = "Cancel";
#[derive(Debug)]
#[allow(dead_code)]
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
pub enum Variant {
Primary,
Center,
Aside,
}
impl Variant {
pub fn to_class_name(&self) -> &str {
match self {
Variant::Center => "center",
Variant::Aside => "aside",
}
}
pub fn to_icon_class_name(&self) -> &str {
match self {
Variant::Center => "modalVariantCenter",
Variant::Aside => "modalVariantAside",
}
}
}
#[derive(Debug)]
pub struct StyledModal {
pub variant: Variant,
pub title: String,
pub message: String,
pub confirm_text: String,
pub cancel_text: String,
pub on_confirm: Option<EventHandler<Msg>>,
}
impl StyledModal {
pub fn build() -> StyledModalBuilder {
StyledModalBuilder::default()
}
pub width: usize,
pub with_icon: bool,
pub children: Vec<Node<Msg>>,
pub class_list: Vec<String>,
}
impl ToNode for StyledModal {
@ -36,14 +42,19 @@ impl ToNode for StyledModal {
}
}
impl StyledModal {
pub fn build() -> StyledModalBuilder {
Default::default()
}
}
#[derive(Default)]
pub struct StyledModalBuilder {
variant: Option<Variant>,
title: Option<String>,
message: Option<String>,
confirm_text: Option<String>,
cancel_text: Option<String>,
on_confirm: Option<Option<EventHandler<Msg>>>,
width: Option<usize>,
with_icon: Option<bool>,
children: Option<Vec<Node<Msg>>>,
class_list: Vec<String>,
}
impl StyledModalBuilder {
@ -52,53 +63,33 @@ impl StyledModalBuilder {
self
}
pub fn title<S>(mut self, title: S) -> Self
where
S: Into<String>,
{
self.title = Some(title.into());
pub fn width(mut self, width: usize) -> Self {
self.width = Some(width);
self
}
pub fn message<S>(mut self, message: S) -> Self
where
S: Into<String>,
{
self.message = Some(message.into());
pub fn with_icon(mut self, with_icon: bool) -> Self {
self.with_icon = Some(with_icon);
self
}
pub fn confirm_text<S>(mut self, confirm_text: S) -> Self
where
S: Into<String>,
{
self.confirm_text = Some(confirm_text.into());
pub fn children(mut self, children: Vec<Node<Msg>>) -> Self {
self.children = Some(children);
self
}
pub fn cancel_text<S>(mut self, cancel_text: S) -> Self
where
S: Into<String>,
{
self.cancel_text = Some(cancel_text.into());
self
}
pub fn on_confirm(mut self, on_confirm: EventHandler<Msg>) -> Self {
self.on_confirm = Some(Some(on_confirm));
pub fn add_class(mut self, name: String) -> Self {
self.class_list.push(name);
self
}
pub fn build(self) -> StyledModal {
StyledModal {
variant: self.variant.unwrap_or_else(|| Variant::Primary),
title: self.title.unwrap_or_else(|| TITLE.to_string()),
message: self.message.unwrap_or_else(|| MESSAGE.to_string()),
confirm_text: self
.confirm_text
.unwrap_or_else(|| CONFIRM_TEXT.to_string()),
cancel_text: self.cancel_text.unwrap_or_else(|| CANCEL_TEXT.to_string()),
on_confirm: None,
variant: self.variant.unwrap_or_else(|| Variant::Center),
width: self.width.unwrap_or_else(|| 130),
with_icon: self.with_icon.unwrap_or_default(),
children: self.children.unwrap_or_default(),
class_list: self.class_list,
}
}
}
@ -106,14 +97,41 @@ impl StyledModalBuilder {
pub fn render(values: StyledModal) -> Node<Msg> {
let StyledModal {
variant,
title,
message,
confirm_text,
cancel_text,
on_confirm,
width,
with_icon,
children,
mut class_list,
} = values;
let icon = if with_icon {
StyledIcon::build(Icon::Close)
.add_class(variant.to_icon_class_name().to_string())
.build()
.into_node()
} else {
empty![]
};
let close_handler = mouse_ev(Ev::Click, |_| Msg::ModalDropped);
let body_handler = mouse_ev(Ev::Click, |ev| {
ev.stop_propagation();
Msg::NoOp
});
let clickable_class = format!("clickableOverlay {}", variant.to_class_name());
class_list.push(format!("styledModal {}", variant.to_class_name()));
let styled_modal_style = format!("max-width: {width}px", width = width);
div![
attrs![At::Class => "modal"],
div![attrs![At::Class => "styledModal"]]
div![
attrs![At::Class => clickable_class],
close_handler,
div![
attrs![At::Class => class_list.join(" "), At::Style => styled_modal_style],
body_handler,
icon,
children
]
]
]
}

View File

@ -10,11 +10,11 @@ export default createGlobalStyle`
}
body {
color: ${color.textDarkest};
color: ${ color.textDarkest };
-webkit-tap-highlight-color: transparent;
line-height: 1.2;
font-size: 16px
${font.regular}
${ font.regular };font-weight: normal;
}
#root {
@ -27,7 +27,7 @@ export default createGlobalStyle`
optgroup,
select,
textarea {
${font.regular}
${ font.regular };font-weight: normal;
}
*, *:after, *:before, input[type="search"] {
@ -83,17 +83,17 @@ export default createGlobalStyle`
display: none;
}
select option {
color: ${color.textDarkest};
color: ${ color.textDarkest };
}
p {
line-height: 1.4285;
a {
cursor: pointer;
color: ${color.textLink};
${font.medium}
color: ${ color.textLink };
${ font.medium };font-weight: normal;
&:hover, &:visited, &:active {
color: ${color.textLink};
color: ${ color.textLink };
}
&:hover {
text-decoration: underline;

View File

@ -48,12 +48,12 @@ export const CloseIcon = styled(Icon)`
export const Title = styled.div`
padding-right: 22px;
font-size: 15px
${font.medium}
${ font.medium };font-weight: normal;
`;
export const Message = styled.div`
padding: 8px 10px 0 0;
white-space: pre-wrap;
font-size: 14px
${font.medium}
${ font.medium };font-weight: normal;
`;

View File

@ -10,5 +10,5 @@ export const Header = styled.div`
export const BoardName = styled.div`
font-size: 24px
${font.medium}
${ font.medium };font-weight: normal;
`;

View File

@ -23,8 +23,8 @@ export const Username = styled.div`
display: inline-block;
padding-right: 12px;
padding-bottom: 10px;
color: ${color.textDark};
${font.medium}
color: ${ color.textDark };
${ font.medium };font-weight: normal;
`;
export const CreatedAt = styled.div`

View File

@ -7,6 +7,6 @@ export const Comments = styled.div`
`;
export const Title = styled.div`
${font.medium}
${ font.medium };font-weight: normal;
font-size: 15px
`;

View File

@ -5,7 +5,7 @@ import { color, font } from '../../../../shared/utils/styles';
export const Title = styled.div`
padding: 20px 0 6px;
font-size: 15px
${font.medium}
${ font.medium };font-weight: normal;
`;
export const EmptyLabel = styled.div`

View File

@ -19,7 +19,7 @@ export const ModalContents = styled.div`
export const ModalTitle = styled.div`
padding-bottom: 14px;
${font.medium}
${ font.medium };font-weight: normal;
font-size: 20px
`;
@ -35,8 +35,8 @@ export const InputCont = styled.div`
export const InputLabel = styled.div`
padding-bottom: 5px;
color: ${color.textMedium};
${font.medium};
color: ${ color.textMedium };
${ font.medium };font-weight: normal;;
font-size: 13px;
`;

View File

@ -17,16 +17,16 @@ export const TitleTextarea = styled(Textarea)`
box-shadow: 0 0 0 1px transparent;
transition: background 0.1s;
font-size: 24px
${font.medium}
${ font.medium };font-weight: normal;
&:hover:not(:focus) {
background: ${color.backgroundLight};
background: ${ color.backgroundLight };
}
}
`;
export const ErrorText = styled.div`
padding-top: 4px;
color: ${color.danger};
color: ${ color.danger };
font-size: 13px
${font.medium}
${ font.medium };font-weight: normal;
`;

View File

@ -87,7 +87,7 @@ export const NoResults = styled.div`
export const NoResultsTitle = styled.div`
padding-top: 30px;
${font.medium}
${ font.medium };font-weight: normal;
font-size: 20px
`;

View File

@ -16,7 +16,7 @@ export const FormElement = styled(Form.Element)`
export const FormHeading = styled.h1`
padding: 6px 0 15px;
font-size: 24px
${font.medium}; font-weight: normal;
${ font.medium };font-weight: normal;; font-weight: normal;
`;
export const ActionButton = styled(Button)`

View File

@ -44,9 +44,9 @@ export const ProjectTexts = styled.div`
`;
export const ProjectName = styled.div`
color: ${color.textDark};
color: ${ color.textDark };
font-size: 15px;
${font.medium};
${ font.medium };font-weight: normal;;
`;
export const ProjectCategory = styled.div`

View File

@ -12,14 +12,14 @@ export const Image = styled.div`
export const Letter = styled.div`
display: inline-block;
width: ${props => props.size}px;
height: ${props => props.size}px;
width: ${ props => props.size }px;
height: ${ props => props.size }px;
border-radius: 100%;
text-transform: uppercase;
color: #fff;
background: ${props => props.color};
${font.medium}
${props => font.size(Math.round(props.size / 1.7))}
background: ${ props => props.color };
${ font.medium };font-weight: normal;
${ props => font.size(Math.round(props.size / 1.7)) }
& > span {
display: flex;
align-items: center;

View File

@ -27,16 +27,16 @@ export const StyledButton = styled.button`
const colored = css`
color: #fff;
background: ${props => color[props.variant]};
${font.medium}
background: ${ props => color[props.variant] };
${ font.medium };font-weight: normal;
&:not(:disabled) {
&:hover {
background: ${props => mixin.lighten(color[props.variant], 0.15)};
background: ${ props => mixin.lighten(color[props.variant], 0.15) };
}
&:active {
background: ${props => mixin.darken(color[props.variant], 0.1)};
background: ${ props => mixin.darken(color[props.variant], 0.1) };
}
${props =>
${ props =>
props.isActive &&
css`
background: ${mixin.darken(color[props.variant], 0.1)} !important;
@ -45,17 +45,17 @@ const colored = css`
`;
const secondaryAndEmptyShared = css`
color: ${color.textDark};
${font.regular}
color: ${ color.textDark };
${ font.regular };font-weight: normal;
&:not(:disabled) {
&:hover {
background: ${color.backgroundLight};
background: ${ color.backgroundLight };
}
&:active {
color: ${color.primary};
background: ${color.backgroundLightPrimary};
color: ${ color.primary };
background: ${ color.backgroundLightPrimary };
}
${props =>
${ props =>
props.isActive &&
css`
color: ${color.primary};

View File

@ -10,7 +10,7 @@ export const StyledConfirmModal = styled(Modal)`
export const Title = styled.div`
padding-bottom: 25px;
${font.medium}
${ font.medium };font-weight: normal;
font-size: 22px
line-height: 1.5;
`;

View File

@ -9,8 +9,8 @@ export const StyledField = styled.div`
export const FieldLabel = styled.label`
display: block;
padding-bottom: 5px;
color: ${color.textMedium};
${font.medium}
color: ${ color.textMedium };
${ font.medium };font-weight: normal;
font-size: 13px
`;
@ -23,7 +23,7 @@ export const FieldTip = styled.div`
export const FieldError = styled.div`
margin-top: 6px;
line-height: 1;
color: ${color.danger};
${font.medium}
color: ${ color.danger };
${ font.medium };font-weight: normal;
font-size: 12.5px
`;

View File

@ -15,20 +15,20 @@ export const InputElement = styled.input`
width: 100%;
padding: 0 7px;
border-radius: 3px;
border: 1px solid ${color.borderLightest};
color: ${color.textDarkest};
background: ${color.backgroundLightest};
border: 1px solid ${ color.borderLightest };
color: ${ color.textDarkest };
background: ${ color.backgroundLightest };
transition: background 0.1s;
${font.regular}
${ font.regular };font-weight: normal;
font-size: 15px
${props => props.hasIcon && 'padding-left: 32px;'}
${ props => props.hasIcon && 'padding-left: 32px;' }
&:hover {
background: ${color.backgroundLight};
background: ${ color.backgroundLight };
}
&:focus {
background: #fff;
border: 1px solid ${color.borderInputFocus};
box-shadow: 0 0 0 1px ${color.borderInputFocus};
border: 1px solid ${ color.borderInputFocus };
box-shadow: 0 0 0 1px ${ color.borderInputFocus };
}
${props =>
props.invalid &&

View File

@ -5,5 +5,5 @@ import { font } from 'shared/utils/styles';
export const Content = styled.div`
padding: 0 !important;
font-size: 15px
${font.regular}
${ font.regular };font-weight: normal;
`;

View File

@ -5,16 +5,16 @@ import { color, font } from 'shared/utils/styles';
export const EditorCont = styled.div`
.ql-toolbar.ql-snow {
border-radius: 4px 4px 0 0;
border: 1px solid ${color.borderLightest};
border: 1px solid ${ color.borderLightest };
border-bottom: none;
}
.ql-container.ql-snow {
border-radius: 0 0 4px 4px;
border: 1px solid ${color.borderLightest};
border: 1px solid ${ color.borderLightest };
border-top: none;
color: ${color.textDarkest};
color: ${ color.textDarkest };
font-size: 15px
${font.regular}
${ font.regular };font-weight: normal;
}
.ql-editor {
min-height: 110px;

View File

@ -10,17 +10,17 @@ export const StyledTextarea = styled.div`
width: 100%;
padding: 8px 12px 9px;
border-radius: 3px;
border: 1px solid ${color.borderLightest};
color: ${color.textDarkest};
background: ${color.backgroundLightest};
${font.regular}
border: 1px solid ${ color.borderLightest };
color: ${ color.textDarkest };
background: ${ color.backgroundLightest };
${ font.regular };font-weight: normal;
font-size: 15px
&:focus {
background: #fff;
border: 1px solid ${color.borderInputFocus};
box-shadow: 0 0 0 1px ${color.borderInputFocus};
border: 1px solid ${ color.borderInputFocus };
box-shadow: 0 0 0 1px ${ color.borderInputFocus };
}
${props =>
${ props =>
props.invalid &&
css`
&,

View File

@ -66,11 +66,11 @@ export const zIndexValues = {
};
export const font = {
regular: 'font-family: "CircularStdBook"; font-weight: normal;',
medium: 'font-family: "CircularStdMedium"; font-weight: normal;',
bold: 'font-family: "CircularStdBold"; font-weight: normal;',
black: 'font-family: "CircularStdBlack"; font-weight: normal;',
size: size => `font-size: ${size}px;`,
regular: 'font-family: "CircularStdBook";',
medium: 'font-family: "CircularStdMedium";',
bold: 'font-family: "CircularStdBold";',
black: 'font-family: "CircularStdBlack";',
size: size => `font-size: ${ size }px;`,
};
export const mixin = {
@ -155,10 +155,10 @@ export const mixin = {
`,
link: (colorValue = color.textLink) => css`
cursor: pointer;
color: ${colorValue};
${font.medium}
color: ${ colorValue };
${ font.medium };font-weight: normal;
&:hover, &:visited, &:active {
color: ${colorValue};
color: ${ colorValue };
}
&:hover {
text-decoration: underline;