Add modal and initial select

This commit is contained in:
Adrian Woźniak 2020-04-01 18:30:01 +02:00
parent 4a9ba8e2a3
commit 28d35026ef
23 changed files with 460 additions and 140 deletions

View File

@ -0,0 +1,38 @@
.issueDetails > .content {
display: flex;
padding: 0 30px 60px;
}
.issueDetails > .content > .left {
width: 65%;
padding-right: 50px;
}
.issueDetails > .content > .right {
width: 35%;
padding-top: 5px;
}
.issueDetails > .topActions {
display: flex;
justify-content: space-between;
padding: 21px 18px 0;
}
.issueDetails > .topActions > .topActionsRight {
display: flex;
align-items: center;
}
.issueDetails > .topActions > .topActionsRight > * {
margin-left: 4px;
}
.issueDetails > .sectionTitle {
margin: 24px 0 5px;
text-transform: uppercase;
color: var(--textMedium);
font-size: 12.5px;
font-family: "CircularStdBold", serif;
font-weight: normal
}

View File

@ -0,0 +1,98 @@
.styledSelect {
position: relative;
border-radius: 4px;
cursor: pointer;
font-size: 14px
}
.styledSelect.normal {
width: 100%;
border: 1px solid var(--borderLightest);
background: var(--backgroundLightest);
transition: background 0.1s;
}
.styledSelect.empty {
display: inline-block;
}
.styledSelect:hover {
background: var(--backgroundLight);
}
.styledSelect:focus {
outline: none;
}
.styledSelect.normal:focus {
border: 1px solid var(--borderInputFocus);
box-shadow: 0 0 0 1px var(--borderInputFocus);
background: #fff;
}
.styledSelect.invalid, .styledSelect.invalid:focus {
border: 1px solid var(--danger);
box-shadow: none;
}
.styledSelect > .dropDownInput {
padding: 10px 14px 8px;
width: 100%;
border: none;
color: var(--textDarkest);
background: none;
}
.styledSelect > .dropDownInput:focus {
outline: none;
}
.styledSelect > .options {
max-height: 200px;
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.styledSelect > .options::-webkit-scrollbar {
width: 8px;
}
.styledSelect > .options::-webkit-scrollbar-track {
background: none;
}
.styledSelect > .options::-webkit-scrollbar-thumb {
border-radius: 99px;
background: var(--backgroundMedium);
}
.styledSelect > .options > .option {
padding: 8px 14px;
word-break: break-word;
cursor: pointer;
}
.styledSelect > .options > .option:last-of-type {
margin-bottom: 8px;
}
.styledSelect > .options > .option.jira-select-option-is-active {
background: var(--backgroundLightPrimary);
}
.styledSelect > .noOptions {
padding: 5px 15px 15px;
color: var(--textLight);
}
.styledSelect > .styledIcon {
position: absolute;
top: 4px;
right: 7px;
padding: 5px;
font-size: 16px;
color: var(--textMedium);
cursor: pointer;
user-select: none;
}

View File

@ -4,10 +4,12 @@
@import "css/global.css"; @import "css/global.css";
@import "css/sidebar.css"; @import "css/sidebar.css";
@import "css/aside.css"; @import "css/aside.css";
@import "css/icon.css"; @import "css/styledIcon.css";
@import "css/shared.css"; @import "css/shared.css";
@import "css/styledTooltip.css"; @import "css/styledTooltip.css";
@import "css/styledAvatar.css"; @import "css/styledAvatar.css";
@import "css/styledSelect.css";
@import "css/app.css"; @import "css/app.css";
@import "css/modal.css"; @import "css/modal.css";
@import "css/issue.css";
@import "css/project.css"; @import "css/project.css";

View File

@ -37,11 +37,13 @@ pub enum Msg {
// dragging // dragging
IssueDragStarted(IssueId), IssueDragStarted(IssueId),
IssueDragStopped(IssueId), IssueDragStopped(IssueId),
IssueDragOver(f64, f64),
IssueDropZone(IssueStatus), IssueDropZone(IssueStatus),
// issues // issues
IssueUpdateResult(FetchObject<String>), IssueUpdateResult(FetchObject<String>),
// modals
CloseModal,
} }
fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) { fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
@ -52,6 +54,9 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
Msg::ChangePage(page) => { Msg::ChangePage(page) => {
model.page = page; model.page = page;
} }
Msg::CloseModal => {
model.modal = None;
}
_ => (), _ => (),
} }
crate::shared::update(&msg, model, orders); crate::shared::update(&msg, model, orders);

View File

@ -9,6 +9,11 @@ use crate::{IssueId, UserId, HOST_URL};
pub type ProjectId = i32; pub type ProjectId = i32;
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialOrd, PartialEq)]
pub enum ModalType {
EditIssue(IssueId),
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialOrd, PartialEq)] #[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialOrd, PartialEq)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum Page { pub enum Page {
@ -47,12 +52,6 @@ pub struct UpdateProjectForm {
pub fields: UpdateProjectPayload, pub fields: UpdateProjectPayload,
} }
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Point {
pub x: f64,
pub y: f64,
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct ProjectPage { pub struct ProjectPage {
pub about_tooltip_visible: bool, pub about_tooltip_visible: bool,
@ -61,7 +60,6 @@ pub struct ProjectPage {
pub only_my_filter: bool, pub only_my_filter: bool,
pub recently_updated_filter: bool, pub recently_updated_filter: bool,
pub dragged_issue_id: Option<IssueId>, pub dragged_issue_id: Option<IssueId>,
pub drag_point: Point,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -77,6 +75,7 @@ pub struct Model {
pub page: Page, pub page: Page,
pub host_url: String, pub host_url: String,
pub project_page: ProjectPage, pub project_page: ProjectPage,
pub modal: Option<ModalType>,
} }
impl Default for Model { impl Default for Model {
@ -100,8 +99,8 @@ impl Default for Model {
only_my_filter: false, only_my_filter: false,
recently_updated_filter: false, recently_updated_filter: false,
dragged_issue_id: None, dragged_issue_id: None,
drag_point: Point::default(),
}, },
modal: None,
} }
} }
} }

View File

@ -2,11 +2,13 @@ use seed::{prelude::*, *};
use jirs_data::*; use jirs_data::*;
use crate::model::{Icon, Model, Page}; use crate::model::{Icon, ModalType, Model, Page};
use crate::shared::modal::{Modal, Variant as ModalVariant};
use crate::shared::styled_avatar::StyledAvatar; use crate::shared::styled_avatar::StyledAvatar;
use crate::shared::styled_button::{StyledButton, Variant}; use crate::shared::styled_button::{StyledButton, Variant as ButtonVariant};
use crate::shared::styled_input::StyledInput; use crate::shared::styled_input::StyledInput;
use crate::shared::{drag_ev, inner_layout, ToNode}; use crate::shared::styled_select::StyledSelect;
use crate::shared::{drag_ev, find_issue, inner_layout, ToNode};
use crate::Msg; use crate::Msg;
pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) { pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
@ -19,6 +21,15 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
.skip() .skip()
.perform_cmd(crate::api::fetch_current_user(model.host_url.clone())); .perform_cmd(crate::api::fetch_current_user(model.host_url.clone()));
} }
Msg::ChangePage(Page::EditIssue(issue_id)) => {
orders
.skip()
.perform_cmd(crate::api::fetch_current_project(model.host_url.clone()));
orders
.skip()
.perform_cmd(crate::api::fetch_current_user(model.host_url.clone()));
model.modal = Some(ModalType::EditIssue(issue_id));
}
Msg::ToggleAboutTooltip => { Msg::ToggleAboutTooltip => {
model.project_page.about_tooltip_visible = !model.project_page.about_tooltip_visible; model.project_page.about_tooltip_visible = !model.project_page.about_tooltip_visible;
} }
@ -58,10 +69,6 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
Msg::IssueDragStopped(_) => { Msg::IssueDragStopped(_) => {
model.project_page.dragged_issue_id = None; model.project_page.dragged_issue_id = None;
} }
Msg::IssueDragOver(x, y) => {
model.project_page.drag_point.x = x;
model.project_page.drag_point.y = y;
}
Msg::IssueDropZone(status) => { Msg::IssueDropZone(status) => {
match ( match (
model.project_page.dragged_issue_id.as_ref().cloned(), model.project_page.dragged_issue_id.as_ref().cloned(),
@ -116,6 +123,25 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
} }
pub fn view(model: &Model) -> Node<Msg> { pub fn view(model: &Model) -> Node<Msg> {
let modal = match model.modal {
Some(ModalType::EditIssue(issue_id)) => {
if let Some(issue) = find_issue(model, issue_id) {
let details = issue_details(model, issue);
let modal = Modal {
variant: ModalVariant::Center,
width: 1040,
with_icon: false,
children: vec![details],
}
.into_node();
Some(modal)
} else {
None
}
}
_ => None,
};
let project_section = vec![ let project_section = vec![
breadcrumbs(model), breadcrumbs(model),
header(), header(),
@ -123,7 +149,7 @@ pub fn view(model: &Model) -> Node<Msg> {
project_board_lists(model), project_board_lists(model),
]; ];
inner_layout(model, "projectPage", project_section) inner_layout(model, "projectPage", project_section, modal)
} }
fn breadcrumbs(model: &Model) -> Node<Msg> { fn breadcrumbs(model: &Model) -> Node<Msg> {
@ -144,7 +170,7 @@ fn breadcrumbs(model: &Model) -> Node<Msg> {
fn header() -> Node<Msg> { fn header() -> Node<Msg> {
let button = StyledButton { let button = StyledButton {
variant: Variant::Secondary, variant: ButtonVariant::Secondary,
icon_only: false, icon_only: false,
disabled: false, disabled: false,
active: false, active: false,
@ -175,7 +201,7 @@ fn project_board_filters(model: &Model) -> Node<Msg> {
let project_page = &model.project_page; let project_page = &model.project_page;
let only_my = StyledButton { let only_my = StyledButton {
variant: Variant::Empty, variant: ButtonVariant::Empty,
icon_only: false, icon_only: false,
disabled: false, disabled: false,
active: model.project_page.only_my_filter, active: model.project_page.only_my_filter,
@ -186,7 +212,7 @@ fn project_board_filters(model: &Model) -> Node<Msg> {
.into_node(); .into_node();
let recently_updated = StyledButton { let recently_updated = StyledButton {
variant: Variant::Empty, variant: ButtonVariant::Empty,
icon_only: false, icon_only: false,
disabled: false, disabled: false,
active: model.project_page.recently_updated_filter, active: model.project_page.recently_updated_filter,
@ -345,8 +371,10 @@ fn project_issue(model: &Model, project: &FullProject, issue: &Issue) -> Node<Ms
class_list.push("hidden"); class_list.push("hidden");
} }
let href = format!("/issues/{id}", id = issue_id);
a![ a![
attrs![At::Class => "issueLink"], attrs![At::Class => "issueLink"; At::Href => href],
div![ div![
attrs![At::Class => class_list.join(" "), At::Draggable => true], attrs![At::Class => class_list.join(" "), At::Draggable => true],
drag_started, drag_started,
@ -363,3 +391,43 @@ fn project_issue(model: &Model, project: &FullProject, issue: &Issue) -> Node<Ms
] ]
] ]
} }
impl ToNode for IssueType {
fn into_node(self) -> Node<Msg> {
div![self.to_string()]
}
}
fn issue_details(_model: &Model, _issue: &Issue) -> Node<Msg> {
let issue_type_select = StyledSelect {
on_change: mouse_ev(Ev::Click, |_| Msg::NoOp),
variant: crate::shared::styled_select::Variant::Empty,
width: 150,
name: None,
placeholder: None,
valid: false,
is_multi: false,
allow_clear: true,
options: vec![IssueType::Story, IssueType::Task, IssueType::Bug],
}
.into_node();
div![
attrs![At::Class => "issueDetails"],
div![
attrs![At::Class => "topActions"],
issue_type_select,
div![attrs![At::Class => "topActionsRight"]],
],
div![
attrs![At::Class => "content"],
div![
attrs![At::Class => "left"],
div![attrs![At::Class => "title"]],
div![attrs![At::Class => "description"]],
div![attrs![At::Class => "comments"]],
],
div![attrs![At::Class => "right"]],
],
]
}

View File

@ -8,5 +8,5 @@ pub fn update(_msg: Msg, _model: &mut model::Model, _orders: &mut impl Orders<Ms
pub fn view(model: &model::Model) -> Node<Msg> { pub fn view(model: &model::Model) -> Node<Msg> {
let project_section = vec![]; let project_section = vec![];
inner_layout(model, "projectSettings", project_section) inner_layout(model, "projectSettings", project_section, None)
} }

View File

@ -1,8 +1,10 @@
use seed::{prelude::*, *}; use seed::{prelude::*, *};
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use jirs_data::Issue;
use crate::model::{Icon, Model}; use crate::model::{Icon, Model};
use crate::Msg; use crate::{IssueId, Msg};
pub mod aside; pub mod aside;
pub mod modal; pub mod modal;
@ -10,8 +12,16 @@ pub mod navbar_left;
pub mod styled_avatar; pub mod styled_avatar;
pub mod styled_button; pub mod styled_button;
pub mod styled_input; pub mod styled_input;
pub mod styled_select;
pub mod styled_tooltip; pub mod styled_tooltip;
pub fn find_issue(model: &Model, issue_id: IssueId) -> Option<&Issue> {
match model.project.as_ref() {
Some(p) => p.issues.iter().find(|issue| issue.id == issue_id),
_ => None,
}
}
pub trait ToNode { pub trait ToNode {
fn into_node(self) -> Node<Msg>; fn into_node(self) -> Node<Msg>;
} }
@ -24,8 +34,18 @@ pub fn divider() -> Node<Msg> {
div![attrs![At::Class => "divider"], ""] div![attrs![At::Class => "divider"], ""]
} }
pub fn inner_layout(model: &Model, page_name: &str, children: Vec<Node<Msg>>) -> Node<Msg> { pub fn inner_layout(
model: &Model,
page_name: &str,
children: Vec<Node<Msg>>,
modal: Option<Node<Msg>>,
) -> Node<Msg> {
let modal_node = match modal {
Some(modal) => vec![modal],
_ => vec![],
};
article![ article![
modal_node,
attrs![At::Class => "inner-layout"], attrs![At::Class => "inner-layout"],
id![page_name], id![page_name],
navbar_left::render(model), navbar_left::render(model),

View File

@ -48,6 +48,7 @@ pub fn render(values: Modal) -> Node<Msg> {
with_icon, with_icon,
children, children,
} = values; } = values;
let icon = if with_icon { let icon = if with_icon {
let mut styled_icon = styled_icon(Icon::Close); let mut styled_icon = styled_icon(Icon::Close);
styled_icon.add_class(variant.to_icon_class_name().to_string()); styled_icon.add_class(variant.to_icon_class_name().to_string());
@ -56,6 +57,12 @@ pub fn render(values: Modal) -> Node<Msg> {
empty![] empty![]
}; };
let close_handler = mouse_ev(Ev::Click, |_| Msg::CloseModal);
let body_handler = mouse_ev(Ev::Click, |ev| {
ev.stop_propagation();
Msg::NoOp
});
let clickable_class = format!("clickableOverlay {}", variant.to_class_name()); let clickable_class = format!("clickableOverlay {}", variant.to_class_name());
let styled_modal_class = format!("styledModal {}", 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); let styled_modal_style = format!("max-width: {width}px", width = width);
@ -63,8 +70,10 @@ pub fn render(values: Modal) -> Node<Msg> {
attrs![At::Class => "modal"], attrs![At::Class => "modal"],
div![ div![
attrs![At::Class => clickable_class], attrs![At::Class => clickable_class],
close_handler,
div![ div![
attrs![At::Class => styled_modal_class, At::Style => styled_modal_style], attrs![At::Class => styled_modal_class, At::Style => styled_modal_style],
body_handler,
icon, icon,
children children
] ]

View File

@ -0,0 +1,93 @@
use seed::{prelude::*, *};
use crate::shared::ToNode;
use crate::Msg;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Variant {
Empty,
Normal,
}
pub struct StyledSelect<Child>
where
Child: ToNode,
{
pub on_change: EventHandler<Msg>,
pub variant: Variant,
pub width: usize,
pub name: Option<String>,
pub placeholder: Option<String>,
pub valid: bool,
pub is_multi: bool,
pub allow_clear: bool,
pub options: Vec<Child>,
}
impl<Child> ToNode for StyledSelect<Child>
where
Child: ToNode,
{
fn into_node(self) -> Node<Msg> {
render(self)
}
}
pub fn render<Child>(values: StyledSelect<Child>) -> Node<Msg>
where
Child: ToNode,
{
let StyledSelect {
on_change,
variant,
width,
name,
placeholder,
valid,
is_multi,
allow_clear,
options,
} = values;
let select_style = format!("width: {width}px", width = width);
let mut select_class = vec!["styledSelect"];
if !valid {
select_class.push("invalid");
}
let children: Vec<Node<Msg>> = options
.into_iter()
.map(|child| render_option(child.into_node()))
.collect();
let clear_icon = match allow_clear {
true => crate::shared::styled_icon(crate::model::Icon::Close),
false => empty![],
};
seed::div![
on_change.clone(),
attrs![At::Class => "styledSelect", At::Style => select_style],
seed::input![
attrs![
At::Class => "dropDownInput",
At::Type => "text"
At::Placeholder => "Search"
At::AutoFocus => true,
],
on_change,
],
clear_icon,
seed::div![
attrs![
At::Class => "options",
],
children
],
seed::div![attrs![At::Class => "noOptions"], "No results"]
]
}
pub fn render_option(content: Node<Msg>) -> Node<Msg> {
seed::div![attrs![At::Class => "option"], content,]
}

View File

@ -49,7 +49,7 @@ export default createGlobalStyle`
} }
h1, h2, h3, h4, h5, h6, strong { h1, h2, h3, h4, h5, h6, strong {
${font.bold} font-family: "CircularStdBold"; font-weight: normal
} }
button { button {

View File

@ -1,6 +1,6 @@
import styled from 'styled-components'; import styled from 'styled-components';
import { color, font } from 'shared/utils/styles'; import { color } from 'shared/utils/styles';
export const Tip = styled.div` export const Tip = styled.div`
display: flex; display: flex;
@ -22,6 +22,6 @@ export const TipLetter = styled.span`
border-radius: 2px; border-radius: 2px;
color: ${color.textDarkest}; color: ${color.textDarkest};
background: ${color.backgroundMedium}; background: ${color.backgroundMedium};
${font.bold} font-family: "CircularStdBold"; font-weight: normal
font-size: 12px font-size: 12px
`; `;

View File

@ -1,6 +1,6 @@
import styled from 'styled-components'; import styled from 'styled-components';
import { color, font } from 'shared/utils/styles'; import { color } from 'shared/utils/styles';
export const Content = styled.div` export const Content = styled.div`
display: flex; display: flex;
@ -36,5 +36,5 @@ export const SectionTitle = styled.div`
text-transform: uppercase; text-transform: uppercase;
color: ${color.textMedium}; color: ${color.textMedium};
font-size: 12.5px font-size: 12.5px
${font.bold} font-family: "CircularStdBold"; font-weight: normal
`; `;

View File

@ -1,18 +1,18 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { IssueType, IssueTypeCopy } from 'shared/constants/issues'; import { IssueType, IssueTypeCopy } from '../../../../shared/constants/issues';
import { IssueTypeIcon, Select } from 'shared/components'; import { IssueTypeIcon, Select } from '../../../../shared/components';
import { TypeButton, Type, TypeLabel } from './Styles'; import { Type, TypeButton, TypeLabel } from './Styles';
const propTypes = { const propTypes = {
issue: PropTypes.object.isRequired, issue: PropTypes.object.isRequired,
updateIssue: PropTypes.func.isRequired, updateIssue: PropTypes.func.isRequired,
}; };
const ProjectBoardIssueDetailsType = ({ issue, updateIssue }) => ( const ProjectBoardIssueDetailsType = ({ issue, updateIssue }) => (
<Select <Select
variant="empty" variant="empty"
dropdownWidth={150} dropdownWidth={150}
withClearValue={false} withClearValue={false}

View File

@ -1,7 +1,7 @@
import styled from 'styled-components'; import styled from 'styled-components';
import { color, font, mixin } from 'shared/utils/styles'; import { color, font } from 'shared/utils/styles';
import { InputDebounced, Spinner, Icon } from 'shared/components'; import { Icon, InputDebounced, Spinner } from 'shared/components';
export const IssueSearch = styled.div` export const IssueSearch = styled.div`
padding: 25px 35px 60px; padding: 25px 35px 60px;
@ -76,7 +76,7 @@ export const SectionTitle = styled.div`
padding-bottom: 12px; padding-bottom: 12px;
text-transform: uppercase; text-transform: uppercase;
color: ${color.textMedium}; color: ${color.textMedium};
${font.bold} font-family: "CircularStdBold"; font-weight: normal
font-size: 11.5px font-size: 11.5px
`; `;

View File

@ -1,8 +1,8 @@
import styled from 'styled-components'; import styled from 'styled-components';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { font, sizes, color, mixin, zIndexValues } from 'shared/utils/styles'; import { color, mixin, sizes, zIndexValues } from 'shared/utils/styles';
import { Logo } from 'shared/components'; import { Logo } from 'shared/components';
export const NavLeft = styled.aside` export const NavLeft = styled.aside`
z-index: ${zIndexValues.navLeft}; z-index: ${zIndexValues.navLeft};
@ -71,7 +71,7 @@ export const ItemText = styled.div`
text-transform: uppercase; text-transform: uppercase;
transition: all 0.1s; transition: all 0.1s;
transition-property: right, visibility, opacity; transition-property: right, visibility, opacity;
${font.bold} font-family: "CircularStdBold"; font-weight: normal
font-size: 12px font-size: 12px
${NavLeft}:hover & { ${NavLeft}:hover & {
right: 0; right: 0;

View File

@ -90,7 +90,7 @@ export const NotImplemented = styled.div`
background: ${color.backgroundMedium}; background: ${color.backgroundMedium};
opacity: 0; opacity: 0;
font-size: 11.5px; font-size: 11.5px;
${font.bold} font-family: "CircularStdBold"; font-weight: normal
${LinkItem}:hover & { ${LinkItem}:hover & {
opacity: 1; opacity: 1;
} }

View File

@ -31,7 +31,7 @@ export const DateSection = styled.div`
export const SelectedMonthYear = styled.div` export const SelectedMonthYear = styled.div`
display: inline-block; display: inline-block;
padding-left: 7px; padding-left: 7px;
${font.bold} font-family: "CircularStdBold"; font-weight: normal
font-size: 16px font-size: 16px
`; `;

View File

@ -1,16 +1,16 @@
import React, { useState, useRef, useLayoutEffect } from 'react'; import React, { useLayoutEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { uniq } from 'lodash'; import { uniq } from 'lodash';
import { KeyCodes } from 'shared/constants/keyCodes'; import { KeyCodes } from '../../../shared/constants/keyCodes';
import { ClearIcon, Dropdown, DropdownInput, Options, Option, OptionsNoResults } from './Styles'; import { ClearIcon, Dropdown, DropdownInput, Option, Options, OptionsNoResults } from './Styles';
const propTypes = { const propTypes = {
dropdownWidth: PropTypes.number, dropdownWidth: PropTypes.number,
value: PropTypes.any, value: PropTypes.any,
isValueEmpty: PropTypes.bool.isRequired, isValueEmpty: PropTypes.bool.isRequired,
searchValue: PropTypes.string.isRequired, searchValue: PropTypes.string.isRequired,
setSearchValue: PropTypes.func.isRequired, setSearchValue: PropTypes.func.isRequired,
$inputRef: PropTypes.object.isRequired, $inputRef: PropTypes.object.isRequired,
deactivateDropdown: PropTypes.func.isRequired, deactivateDropdown: PropTypes.func.isRequired,
@ -177,12 +177,12 @@ const SelectDropdown = ({
return ( return (
<Dropdown width={dropdownWidth}> <Dropdown width={dropdownWidth}>
<DropdownInput <DropdownInput
type="text" type="text"
placeholder="Search" placeholder="Search"
ref={$inputRef} autoFocus
autoFocus ref={$inputRef}
onKeyDown={handleInputKeyDown} onKeyDown={handleInputKeyDown}
onChange={event => setSearchValue(event.target.value)} onChange={event => setSearchValue(event.target.value)}
/> />
{!isValueEmpty && withClearValue && <ClearIcon type="close" onClick={clearOptionValues} />} {!isValueEmpty && withClearValue && <ClearIcon type="close" onClick={clearOptionValues} />}

View File

@ -1,7 +1,7 @@
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
import { color, mixin, zIndexValues } from 'shared/utils/styles'; import { color, mixin, zIndexValues } from 'shared/utils/styles';
import Icon from 'shared/components/Icon'; import Icon from 'shared/components/Icon';
export const StyledSelect = styled.div` export const StyledSelect = styled.div`
position: relative; position: relative;
@ -119,14 +119,14 @@ export const ClearIcon = styled(Icon)`
font-size: 16px; font-size: 16px;
color: ${color.textMedium}; color: ${color.textMedium};
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
`; `;
export const Options = styled.div` export const Options = styled.div`
max-height: 200px; max-height: 200px;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
-webkit-overflow-scrolling: touch;; -webkit-overflow-scrolling: touch;
${mixin.customScrollbar()}; ${mixin.customScrollbar()};
`; `;

View File

@ -1,64 +1,22 @@
import React, { useState, useRef } from 'react'; import React, { useRef, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import useOnOutsideClick from 'shared/hooks/onOutsideClick'; import useOnOutsideClick from 'shared/hooks/onOutsideClick';
import { KeyCodes } from 'shared/constants/keyCodes'; import { KeyCodes } from 'shared/constants/keyCodes';
import Icon from 'shared/components/Icon'; import Icon from 'shared/components/Icon';
import Dropdown from './Dropdown'; import Dropdown from './Dropdown';
import { import { AddMore, ChevronIcon, Placeholder, StyledSelect, ValueContainer, ValueMulti, ValueMultiItem, } from './Styles';
StyledSelect,
ValueContainer,
ChevronIcon,
Placeholder,
ValueMulti,
ValueMultiItem,
AddMore,
} from './Styles';
const propTypes = {
className: PropTypes.string,
variant: PropTypes.oneOf(['normal', 'empty']),
dropdownWidth: PropTypes.number,
name: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.array, PropTypes.string, PropTypes.number]),
defaultValue: PropTypes.any,
placeholder: PropTypes.string,
invalid: PropTypes.bool,
options: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
onCreate: PropTypes.func,
isMulti: PropTypes.bool,
withClearValue: PropTypes.bool,
renderValue: PropTypes.func,
renderOption: PropTypes.func,
};
const defaultProps = {
className: undefined,
variant: 'normal',
dropdownWidth: undefined,
name: undefined,
value: undefined,
defaultValue: undefined,
placeholder: 'Select',
invalid: false,
onCreate: undefined,
isMulti: false,
withClearValue: true,
renderValue: undefined,
renderOption: undefined,
};
const Select = ({ const Select = ({
className, className,
variant, variant,
dropdownWidth, dropdownWidth,
name, name,
value: propsValue, value: propsValue,
defaultValue, defaultValue,
placeholder, placeholder,
invalid, invalid,
options, options,
onChange, onChange,
onCreate, onCreate,
@ -183,27 +141,57 @@ const Select = ({
{isDropdownOpen && ( {isDropdownOpen && (
<Dropdown <Dropdown
dropdownWidth={dropdownWidth} dropdownWidth={dropdownWidth}
value={value} value={value}
isValueEmpty={isValueEmpty} isValueEmpty={isValueEmpty}
searchValue={searchValue} searchValue={searchValue}
setSearchValue={setSearchValue} setSearchValue={setSearchValue}
$selectRef={$selectRef} $selectRef={$selectRef}
$inputRef={$inputRef} $inputRef={$inputRef}
deactivateDropdown={deactivateDropdown} deactivateDropdown={deactivateDropdown}
options={options} options={options}
onChange={handleChange} onChange={handleChange}
onCreate={onCreate} onCreate={onCreate}
isMulti={isMulti} isMulti={isMulti}
withClearValue={withClearValue} withClearValue={withClearValue}
propsRenderOption={propsRenderOption} propsRenderOption={propsRenderOption}
/> />
)} )}
</StyledSelect> </StyledSelect>
); );
}; };
Select.propTypes = propTypes; Select.propTypes = {
Select.defaultProps = defaultProps; className: PropTypes.string,
variant: PropTypes.oneOf(['normal', 'empty']),
dropdownWidth: PropTypes.number,
name: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.array, PropTypes.string, PropTypes.number]),
defaultValue: PropTypes.any,
placeholder: PropTypes.string,
invalid: PropTypes.bool,
options: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
onCreate: PropTypes.func,
isMulti: PropTypes.bool,
withClearValue: PropTypes.bool,
renderValue: PropTypes.func,
renderOption: PropTypes.func,
};
Select.defaultProps = {
className: undefined,
variant: 'normal',
dropdownWidth: undefined,
name: undefined,
value: undefined,
defaultValue: undefined,
placeholder: 'Select',
invalid: false,
onCreate: undefined,
isMulti: false,
withClearValue: true,
renderValue: undefined,
renderOption: undefined,
};
export default Select; export default Select;

View File

@ -1,7 +1,7 @@
import { css } from 'styled-components'; import { css } from 'styled-components';
import Color from 'color'; import Color from 'color';
import { IssueType, IssueStatus, IssuePriority } from 'shared/constants/issues'; import { IssuePriority, IssueStatus, IssueType } from 'shared/constants/issues';
export const color = { export const color = {
primary: '#0052cc', // Blue primary: '#0052cc', // Blue
@ -174,7 +174,7 @@ export const mixin = {
user-select: none; user-select: none;
color: ${colorValue}; color: ${colorValue};
background: ${background}; background: ${background};
${font.bold} font-family: "CircularStdBold"; font-weight: normal
font-size: 12px font-size: 12px
i { i {
margin-left: 4px; margin-left: 4px;