Feature(Project board): Add text filter input

This commit is contained in:
Adrian Wozniak 2020-03-31 08:56:46 +02:00
parent 567f5e63c5
commit 55a8904210
18 changed files with 486 additions and 259 deletions

View File

@ -10,13 +10,13 @@ main {
min-height: 100%;
}
article#inner-layout {
article.inner-layout {
width: 100%;
}
@media (min-width: 1240px) {
article#inner-layout {
display: flex;
justify-content: start;
}
/*article.inner-layout {*/
/* display: flex;*/
/* justify-content: start;*/
/*}*/
}

View File

@ -0,0 +1,50 @@
#projectPage {
padding: 25px 32px 50px calc(var(--appNavBarLeftWidth) + var(--secondarySideBarWidth) + 40px);
}
#projectPage > .breadcrumbsContainer {
color: var(--textMedium);
font-size: 15px;
}
#projectPage > .breadcrumbsContainer > .breadcrumbsDivider {
position: relative;
top: 2px;
margin: 0 10px;
font-size: 18px;
}
#projectPage > #projectBoardHeader {
margin-top: 6px;
display: flex;
justify-content: space-between;
}
#projectPage > #projectBoardHeader > #boardName {
font-size: 24px;
font-family: var(--font-medium);
font-weight: normal;
}
#projectPage > #projectBoardFilters {
display: flex;
align-items: center;
margin-top: 24px;
}
#projectPage > #projectBoardFilters > #searchInput {
margin-right: 18px;
width: 160px;
}
@media (max-width: 1100px) {
#projectPage {
padding: 25px 20px 50px calc(var(--appNavBarLeftWidth) + var(--secondarySideBarWidth) + 20px);
}
}
@media (max-width: 999px) {
#projectPage {
padding-left: calc(var(--appNavBarLeftWidth) + var(--secondarySideBarWidth) + 20px - var(--secondarySideBarWidth));
}
}

View File

@ -37,7 +37,7 @@
padding: 0 9px;
}
.styledButton.primary {
.styledButton.primary, .styledButton.primary > i {
color: #fff;
background: var(--primary);
font-family: var(--font-medium);
@ -55,17 +55,17 @@
filter: brightness(110%);
}
.styledButton.success {
.styledButton.success, .styledButton.success > i {
color: #fff;
background: var(--success);
}
.styledButton.danger {
.styledButton.danger, .styledButton.danger > i {
color: #fff;
background: var(--danger);
}
.styledButton.secondary {
.styledButton.secondary, .styledButton.secondary > i {
color: var(--textDark);
background: var(--secondary);
font-family: var(--font-regular);
@ -85,7 +85,7 @@
background: var(--backgroundLightPrimary);
}
.styledButton.empty {
.styledButton.empty, .styledButton.empty > i {
background: #fff;
color: var(--textDark);
font-family: var(--font-regular);
@ -104,3 +104,52 @@
color: var(--primary);
background: var(--backgroundLightPrimary);
}
.styledInput {
position: relative;
display: inline-block;
height: 32px;
width: 100%;
}
.styledInput > .inputElement {
height: 100%;
width: 100%;
padding: 0 7px;
border-radius: 3px;
border: 1px solid var(--borderLightest);
color: var(--textDarkest);
background: var(--backgroundLightest);
transition: background 0.1s;
font-family: var(--font-regular);
font-size: 15px;
}
.styledInput > .inputElement.withIcon {
padding-left: 32px;
}
.styledInput > i.styledIcon {
font-size: 15px;
position: absolute;
top: 8px;
left: 8px;
pointer-events: none;
color: #5E6C84;
}
.styledInput > .inputElement:hover {
background: var(--backgroundLight);
}
.styledInput > .inputElement:focus {
background: #fff;
border: 1px solid var(--borderInputFocus);
box-shadow: 0 0 0 1px var(--borderInputFocus);
}
.styledInput.invalid,
.styledInput.invalid:focus {
border: 1px solid var(--danger);
box-shadow: none;
}

View File

@ -8,3 +8,4 @@
@import "css/shared.css";
@import "css/styledTooltip.css";
@import "css/app.css";
@import "css/project.css";

View File

@ -18,6 +18,9 @@ pub enum Msg {
CurrentUserResult(FetchObject<String>),
InternalFailure(String),
ToggleAboutTooltip,
// project
ProjectTextFilterChanged(String),
}
fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {

View File

@ -47,6 +47,12 @@ pub struct UpdateProjectForm {
pub fields: UpdateProjectPayload,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ProjectPage {
pub about_tooltip_visible: bool,
pub text_filter: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Model {
pub access_token: Option<Uuid>,
@ -59,8 +65,7 @@ pub struct Model {
pub comments_by_project_id: HashMap<ProjectId, Vec<Comment>>,
pub page: Page,
pub host_url: String,
//
pub about_tooltip_visible: bool,
pub project_page: ProjectPage,
}
impl Default for Model {
@ -77,7 +82,10 @@ impl Default for Model {
comments_by_project_id: Default::default(),
page: Page::Project,
host_url,
about_tooltip_visible: false,
project_page: ProjectPage {
about_tooltip_visible: false,
text_filter: "".to_string(),
},
}
}
}

View File

@ -1,6 +1,8 @@
use seed::{prelude::*, *};
use crate::model::Page;
use crate::model::{Icon, Model, Page};
use crate::shared::styled_button::{StyledButton, Variant};
use crate::shared::styled_input::StyledInput;
use crate::shared::{host_client, inner_layout};
use crate::Msg;
@ -15,14 +17,64 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
.perform_cmd(crate::api::fetch_current_user(model.host_url.clone()));
}
Msg::ToggleAboutTooltip => {
model.about_tooltip_visible = !model.about_tooltip_visible;
model.project_page.about_tooltip_visible = !model.project_page.about_tooltip_visible;
}
Msg::ProjectTextFilterChanged(text) => {
model.project_page.text_filter = text;
}
_ => (),
}
}
pub fn view(model: &crate::model::Model) -> Node<Msg> {
let project_section = section![id!["project-section"],];
pub fn view(model: &Model) -> Node<Msg> {
let project_section = vec![breadcrumbs(model), header(), project_board_filters(model)];
inner_layout(model, project_section)
inner_layout(model, "projectPage", project_section)
}
fn breadcrumbs(model: &Model) -> Node<Msg> {
let project_name = model
.project
.as_ref()
.map(|p| p.name.clone())
.unwrap_or_default();
div![
attrs![At::Class => "breadcrumbsContainer"],
span!["Projects"],
span![attrs![At::Class => "breadcrumbsDivider"], "/"],
span![project_name],
span![attrs![At::Class => "breadcrumbsDivider"], "/"],
span!["Kanban Board"]
]
}
fn header() -> Node<Msg> {
let button = StyledButton {
variant: Variant::Secondary,
icon_only: false,
disabled: false,
active: false,
text: Some("Github Repo".to_string()),
icon: Some(Icon::Github),
}
.into_node();
div![
id!["projectBoardHeader"],
div![id!["boardName"], "Kanban board"],
a![
attrs![At::Href => "https://gitlab.com/adrian.wozniak/jirs", At::Target => "__blank", At::Rel => "noreferrer noopener"],
button
]
]
}
fn project_board_filters(_model: &Model) -> Node<Msg> {
let search_input = StyledInput {
icon: Some(Icon::Search),
id: Some("searchInput".to_string()),
valid: true,
on_change: input_ev(Ev::Change, |value| Msg::ProjectTextFilterChanged(value)),
}
.into_node();
div![id!["projectBoardFilters"], search_input]
}

View File

@ -1,11 +1,12 @@
use seed::{prelude::*, *};
use crate::shared::inner_layout;
use crate::{model, Msg};
use seed::{prelude::*, *};
pub fn update(_msg: Msg, _model: &mut model::Model, _orders: &mut impl Orders<Msg>) {}
pub fn view(model: &model::Model) -> Node<Msg> {
let project_section = section![id!["project-settings-section"],];
let project_section = vec![];
inner_layout(model, project_section)
inner_layout(model, "projectSettings", project_section)
}

View File

@ -0,0 +1,66 @@
use seed::{prelude::*, *};
use crate::model::{Icon, Model, Page};
use crate::shared::{divider, styled_icon};
use crate::Msg;
pub fn render(model: &Model) -> Node<Msg> {
let project_icon = Node::from_html(include_str!("../../static/project-avatar.svg"));
let project_info = match model.project.as_ref() {
Some(project) => li![
id!["projectInfo"],
project_icon,
div![
attrs![At::Class => "projectTexts";],
div![attrs![At::Class => "projectName";], project.name],
div![attrs![At::Class => "projectCategory";], project.category]
],
],
_ => li![
id!["projectInfo"],
div![
attrs![At::Class => "projectTexts";],
div![attrs![At::Class => "projectName";], ""],
div![attrs![At::Class => "projectCategory";], ""]
],
],
};
nav![
id!["sidebar"],
ul![
project_info,
sidebar_link_item(model, "Kanban Board", Icon::Board, Some(Page::Project)),
sidebar_link_item(
model,
"Project settings",
Icon::Settings,
Some(Page::ProjectSettings)
),
li![divider()],
sidebar_link_item(model, "Releases", Icon::Shipping, None),
sidebar_link_item(model, "Issue and Filters", Icon::Issues, None),
sidebar_link_item(model, "Pages", Icon::Page, None),
sidebar_link_item(model, "Reports", Icon::Reports, None),
sidebar_link_item(model, "Components", Icon::Component, None),
]
]
}
fn sidebar_link_item(model: &Model, name: &str, icon: Icon, page: Option<Page>) -> Node<Msg> {
let path = page.map(|ref p| p.to_path()).unwrap_or_default();
let mut class_list = vec!["linkItem".to_string(), icon.to_string()];
if let None = page {
class_list.push("notAllowed".to_string());
};
if Some(model.page) == page {
class_list.push("active".to_string());
}
li![
attrs![At::Class => class_list.join(" ")],
a![
attrs![At::Href => path],
styled_icon(icon),
div![attrs![At::Class => "linkText"], name],
]
]
}

View File

@ -2,170 +2,16 @@ use seed::fetch::{FetchObject, ResponseWithDataResult};
use seed::{prelude::*, *};
use jirs_data::FullProjectResponse;
use styled_button::*;
use crate::model::{Icon, Model, Page};
use crate::model::{Icon, Model};
use crate::Msg;
pub mod aside;
pub mod navbar_left;
pub mod styled_button;
pub mod styled_input;
pub mod styled_tooltip;
pub fn navbar_left(model: &Model) -> Vec<Node<Msg>> {
let logo_svg = Node::from_html(include_str!("../../static/logo.svg"));
vec![
about_tooltip_popup(model),
aside![
id!["navbar-left"],
a![
attrs![At::Class => "logoLink", At::Href => "/"],
div![attrs![At::Class => "styledLogo"], logo_svg]
],
navbar_left_item(model, "Search issues", Icon::Search),
navbar_left_item(model, "Create Issue", Icon::Plus),
div![
attrs![At::Class => "bottom"],
about_tooltip(model, navbar_left_item(model, "About", Icon::Help)),
],
],
]
}
fn navbar_left_item(_model: &Model, text: &str, logo: Icon) -> Node<Msg> {
div![
attrs![At::Class => "item"],
i![attrs![At::Class => format!("styledIcon {}", logo)]],
span![attrs![At::Class => "itemText"], text]
]
}
pub fn about_tooltip(model: &Model, children: Node<Msg>) -> Node<Msg> {
div![
attrs![At::Class => "aboutTooltip"],
ev(Ev::Click, |_| Msg::ToggleAboutTooltip),
children
]
}
fn about_tooltip_popup(model: &Model) -> Node<Msg> {
styled_tooltip::StyledTooltip {
visible: model.about_tooltip_visible,
class_name: "aboutTooltipPopup".to_string(),
children: div![
ev(Ev::Click, |_| Msg::ToggleAboutTooltip),
attrs![At::Class => "feedbackDropdown"],
div![
attrs![At::Class => "feedbackImageCont"],
img![attrs![At::Src => "/feedback.png", At::Class => "feedbackImage"]]
],
div![
attrs![At::Class => "feedbackParagraph"],
"This simplified Jira clone is built with Seed.rs on the front-end and Actix-Web on the back-end."
],
div![
attrs![At::Class => "feedbackParagraph"],
"Read more on my website or reach out via ",
a![
attrs![At::Href => "mailto:adrian.wozniak@ita-prog.pl"],
strong!["adrian.wozniak@ita-prog.pl"]
]
],
a![
attrs![
At::Href => "https://gitlab.com/adrian.wozniak/jirs",
At::Target => "_blank",
At::Rel => "noreferrer noopener",
],
StyledButton {
text: Some("Visit Website".to_string()),
variant: Variant::Primary,
disabled: false,
active: false,
icon_only: false,
icon: None,
}.into_node(),
],
a![
id!["about-github-button"],
attrs![
At::Href => "https://gitlab.com/adrian.wozniak/jirs",
At::Target => "_blank",
At::Rel => "noreferrer noopener",
],
StyledButton {
text: Some("Github Repo".to_string()),
variant: Variant::Secondary,
disabled: false,
active: false,
icon_only: false,
icon: Some(Icon::Github),
}.into_node()
]
],
}.into_node()
}
pub fn sidebar(model: &Model) -> Node<Msg> {
let project_icon = Node::from_html(include_str!("../../static/project-avatar.svg"));
let project_info = match model.project.as_ref() {
Some(project) => li![
id!["projectInfo"],
project_icon,
div![
attrs![At::Class => "projectTexts";],
div![attrs![At::Class => "projectName";], project.name],
div![attrs![At::Class => "projectCategory";], project.category]
],
],
_ => li![
id!["projectInfo"],
div![
attrs![At::Class => "projectTexts";],
div![attrs![At::Class => "projectName";], ""],
div![attrs![At::Class => "projectCategory";], ""]
],
],
};
nav![
id!["sidebar"],
ul![
project_info,
sidebar_link_item(model, "Kanban Board", Icon::Board, Some(Page::Project)),
sidebar_link_item(
model,
"Project settings",
Icon::Settings,
Some(Page::ProjectSettings)
),
li![divider()],
sidebar_link_item(model, "Releases", Icon::Shipping, None),
sidebar_link_item(model, "Issue and Filters", Icon::Issues, None),
sidebar_link_item(model, "Pages", Icon::Page, None),
sidebar_link_item(model, "Reports", Icon::Reports, None),
sidebar_link_item(model, "Components", Icon::Component, None),
]
]
}
fn sidebar_link_item(model: &Model, name: &str, icon: Icon, page: Option<Page>) -> Node<Msg> {
let path = page.map(|ref p| p.to_path()).unwrap_or_default();
let mut class_list = vec!["linkItem".to_string(), icon.to_string()];
let item_class = if let None = page {
class_list.push("notAllowed".to_string())
};
if Some(model.page) == page {
class_list.push("active".to_string());
}
li![
attrs![At::Class => class_list.join(" ")],
a![
attrs![At::Href => path],
styled_icon(icon),
div![attrs![At::Class => "linkText"], name],
]
]
}
pub fn styled_icon(icon: Icon) -> Node<Msg> {
i![attrs![At::Class => format!("styledIcon {}", icon)], ""]
}
@ -174,11 +20,12 @@ pub fn divider() -> Node<Msg> {
div![attrs![At::Class => "divider"], ""]
}
pub fn inner_layout(model: &Model, children: Node<Msg>) -> Node<Msg> {
pub fn inner_layout(model: &Model, page_name: &str, children: Vec<Node<Msg>>) -> Node<Msg> {
article![
id!["inner-layout"],
navbar_left(model),
sidebar(model),
attrs![At::Class => "inner-layout"],
id![page_name],
navbar_left::render(model),
aside::render(model),
children,
]
}

View File

@ -0,0 +1,101 @@
use seed::{prelude::*, *};
use crate::model::{Icon, Model};
use crate::shared::styled_button::{StyledButton, Variant};
use crate::shared::styled_tooltip;
use crate::Msg;
pub fn render(model: &Model) -> Vec<Node<Msg>> {
let logo_svg = Node::from_html(include_str!("../../static/logo.svg"));
vec![
about_tooltip_popup(model),
aside![
id!["navbar-left"],
a![
attrs![At::Class => "logoLink", At::Href => "/"],
div![attrs![At::Class => "styledLogo"], logo_svg]
],
navbar_left_item(model, "Search issues", Icon::Search),
navbar_left_item(model, "Create Issue", Icon::Plus),
div![
attrs![At::Class => "bottom"],
about_tooltip(model, navbar_left_item(model, "About", Icon::Help)),
],
],
]
}
fn navbar_left_item(_model: &Model, text: &str, logo: Icon) -> Node<Msg> {
div![
attrs![At::Class => "item"],
i![attrs![At::Class => format!("styledIcon {}", logo)]],
span![attrs![At::Class => "itemText"], text]
]
}
pub fn about_tooltip(_model: &Model, children: Node<Msg>) -> Node<Msg> {
div![
attrs![At::Class => "aboutTooltip"],
ev(Ev::Click, |_| Msg::ToggleAboutTooltip),
children
]
}
fn about_tooltip_popup(model: &Model) -> Node<Msg> {
styled_tooltip::StyledTooltip {
visible: model.project_page.about_tooltip_visible,
class_name: "aboutTooltipPopup".to_string(),
children: div![
ev(Ev::Click, |_| Msg::ToggleAboutTooltip),
attrs![At::Class => "feedbackDropdown"],
div![
attrs![At::Class => "feedbackImageCont"],
img![attrs![At::Src => "/feedback.png", At::Class => "feedbackImage"]]
],
div![
attrs![At::Class => "feedbackParagraph"],
"This simplified Jira clone is built with Seed.rs on the front-end and Actix-Web on the back-end."
],
div![
attrs![At::Class => "feedbackParagraph"],
"Read more on my website or reach out via ",
a![
attrs![At::Href => "mailto:adrian.wozniak@ita-prog.pl"],
strong!["adrian.wozniak@ita-prog.pl"]
]
],
a![
attrs![
At::Href => "https://gitlab.com/adrian.wozniak/jirs",
At::Target => "_blank",
At::Rel => "noreferrer noopener",
],
StyledButton {
text: Some("Visit Website".to_string()),
variant: Variant::Primary,
disabled: false,
active: false,
icon_only: false,
icon: None,
}.into_node(),
],
a![
id!["about-github-button"],
attrs![
At::Href => "https://gitlab.com/adrian.wozniak/jirs",
At::Target => "_blank",
At::Rel => "noreferrer noopener",
],
StyledButton {
text: Some("Github Repo".to_string()),
variant: Variant::Secondary,
disabled: false,
active: false,
icon_only: false,
icon: Some(Icon::Github),
}.into_node()
]
],
}.into_node()
}

View File

@ -0,0 +1,55 @@
use seed::{prelude::*, *};
use crate::model::Icon;
use crate::shared::styled_icon;
use crate::Msg;
pub struct StyledInput {
pub id: Option<String>,
pub icon: Option<Icon>,
pub valid: bool,
pub on_change: EventHandler<Msg>,
}
impl Into<Node<Msg>> for StyledInput {
fn into(self) -> Node<Msg> {
render(self)
}
}
impl StyledInput {
pub fn into_node(self) -> Node<Msg> {
self.into()
}
}
pub fn render(values: StyledInput) -> Node<Msg> {
let StyledInput {
id,
icon,
valid,
on_change,
} = values;
let mut wrapper_class_list = vec!["styledInput"];
if !valid {
wrapper_class_list.push("invalid");
}
let mut input_class_list = vec!["inputElement"];
if icon.is_some() {
input_class_list.push("withIcon");
}
let icon = match icon {
Some(icon) => vec![styled_icon(icon)],
_ => vec![],
};
div![
id![id.unwrap_or_default()],
attrs!(At::Class => wrapper_class_list.join(" ")),
icon,
input![attrs![At::Class => input_class_list.join(" ")], on_change]
]
}

View File

@ -2,15 +2,15 @@ import React from 'react';
import { Button } from 'shared/components';
import { Header, BoardName } from './Styles';
import { BoardName, Header } from './Styles';
const ProjectBoardHeader = () => (
<Header>
<BoardName>Kanban board</BoardName>
<a href="https://github.com/oldboyxx/jira_clone" target="_blank" rel="noreferrer noopener">
<Button icon="github">Github Repo</Button>
</a>
</Header>
<Header id='projectHeader'>
<BoardName id='boardName'>Kanban board</BoardName>
<a href="https://github.com/oldboyxx/jira_clone" target="_blank" rel="noreferrer noopener">
<Button icon="github">Github Repo</Button>
</a>
</Header>
);
export default ProjectBoardHeader;

View File

@ -1,6 +1,6 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Route, useRouteMatch, useHistory } from 'react-router-dom';
import { Route, useHistory, useRouteMatch } from 'react-router-dom';
import useMergeState from 'shared/hooks/mergeState';
import { Breadcrumbs, Modal } from 'shared/components';
@ -10,12 +10,6 @@ import Filters from './Filters';
import Lists from './Lists';
import IssueDetails from './IssueDetails';
const propTypes = {
project: PropTypes.object.isRequired,
fetchProject: PropTypes.func.isRequired,
updateLocalProjectIssues: PropTypes.func.isRequired,
};
const defaultFilters = {
searchTerm: '',
userIds: [],
@ -69,6 +63,10 @@ const ProjectBoard = ({ project, fetchProject, updateLocalProjectIssues }) => {
);
};
ProjectBoard.propTypes = propTypes;
ProjectBoard.propTypes = {
project: PropTypes.object.isRequired,
fetchProject: PropTypes.func.isRequired,
updateLocalProjectIssues: PropTypes.func.isRequired,
};
export default ProjectBoard;

View File

@ -1,9 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Icon, AboutTooltip } from 'shared/components';
import { AboutTooltip, Icon } from 'shared/components';
import { NavLeft, LogoLink, StyledLogo, Bottom, Item, ItemText } from './Styles';
import { Bottom, Item, ItemText, LogoLink, NavLeft, StyledLogo } from './Styles';
const propTypes = {
issueSearchModalOpen: PropTypes.func.isRequired,
@ -11,17 +11,17 @@ const propTypes = {
};
const ProjectNavbarLeft = ({ issueSearchModalOpen, issueCreateModalOpen }) => (
<NavLeft>
<LogoLink to="/">
<StyledLogo color="#fff" />
</LogoLink>
<NavLeft class={ 'NavLeft' }>
<LogoLink to="/">
<StyledLogo color="#fff"/>
</LogoLink>
<Item onClick={issueSearchModalOpen}>
<Icon type="search" size={22} top={1} left={3} />
<ItemText>Search issues</ItemText>
</Item>
<Item onClick={ issueSearchModalOpen }>
<Icon type="search" size={ 22 } top={ 1 } left={ 3 }/>
<ItemText>Search issues</ItemText>
</Item>
<Item onClick={issueCreateModalOpen}>
<Item onClick={ issueCreateModalOpen }>
<Icon type="plus" size={27} />
<ItemText>Create Issue</ItemText>
</Item>

View File

@ -1,10 +1,10 @@
import React from 'react';
import { Route, Redirect, useRouteMatch, useHistory } from 'react-router-dom';
import { Redirect, Route, useHistory, useRouteMatch } from 'react-router-dom';
import useApi from 'shared/hooks/api';
import { updateArrayItemById } from 'shared/utils/javascript';
import { createQueryParamModalHelpers } from 'shared/utils/queryParamModal';
import { PageLoader, PageError, Modal } from 'shared/components';
import { Modal, PageError, PageLoader } from 'shared/components';
import NavbarLeft from './NavbarLeft';
import Sidebar from './Sidebar';
@ -38,17 +38,17 @@ const Project = () => {
};
return (
<ProjectPage>
<NavbarLeft
issueSearchModalOpen={issueSearchModalHelpers.open}
issueCreateModalOpen={issueCreateModalHelpers.open}
/>
<ProjectPage id={ 'ProjectPage' }>
<NavbarLeft
issueSearchModalOpen={ issueSearchModalHelpers.open }
issueCreateModalOpen={ issueCreateModalHelpers.open }
/>
<Sidebar project={project} />
<Sidebar project={ project }/>
{issueSearchModalHelpers.isOpen() && (
<Modal
isOpen
{ issueSearchModalHelpers.isOpen() && (
<Modal
isOpen
testid="modal:issue-search"
variant="aside"
width={600}

View File

@ -3,21 +3,19 @@ import PropTypes from 'prop-types';
import { Container, Divider } from './Styles';
const propTypes = {
items: PropTypes.array.isRequired,
};
const Breadcrumbs = ({ items }) => (
<Container>
{items.map((item, index) => (
<Fragment key={item}>
{index !== 0 && <Divider>/</Divider>}
{item}
</Fragment>
))}
<Container id='Breadcrumbs'>
{ items.map((item, index) => (
<Fragment key={ item }>
{ index !== 0 && <Divider>/</Divider> }
{ item }
</Fragment>
)) }
</Container>
);
Breadcrumbs.propTypes = propTypes;
Breadcrumbs.propTypes = {
items: PropTypes.array.isRequired,
};
export default Breadcrumbs;

View File

@ -6,28 +6,6 @@ import Icon from 'shared/components/Icon';
import { StyledButton, StyledSpinner, Text } from './Styles';
const propTypes = {
className: PropTypes.string,
children: PropTypes.node,
variant: PropTypes.oneOf(['primary', 'success', 'danger', 'secondary', 'empty']),
icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
iconSize: PropTypes.number,
disabled: PropTypes.bool,
isWorking: PropTypes.bool,
onClick: PropTypes.func,
};
const defaultProps = {
className: undefined,
children: undefined,
variant: 'secondary',
icon: undefined,
iconSize: 18,
disabled: false,
isWorking: false,
onClick: () => {},
};
const Button = forwardRef(
({ children, variant, icon, iconSize, disabled, isWorking, onClick, ...buttonProps }, ref) => {
const handleClick = () => {
@ -52,17 +30,37 @@ const Button = forwardRef(
<Icon type={icon} size={iconSize} color={getIconColor(variant)} />
) : (
icon
)}
{children && <Text withPadding={isWorking || icon}>{children}</Text>}
) }
{ children && <Text withPadding={ isWorking || icon }>{ children }</Text> }
</StyledButton>
);
},
);
const getIconColor = variant =>
['secondary', 'empty'].includes(variant) ? color.textDark : '#fff';
[ 'secondary', 'empty' ].includes(variant) ? color.textDark : '#fff';
Button.propTypes = propTypes;
Button.defaultProps = defaultProps;
Button.propTypes = {
className: PropTypes.string,
children: PropTypes.node,
variant: PropTypes.oneOf([ 'primary', 'success', 'danger', 'secondary', 'empty' ]),
icon: PropTypes.oneOfType([ PropTypes.string, PropTypes.node ]),
iconSize: PropTypes.number,
disabled: PropTypes.bool,
isWorking: PropTypes.bool,
onClick: PropTypes.func,
};
Button.defaultProps = {
className: undefined,
children: undefined,
variant: 'secondary',
icon: undefined,
iconSize: 18,
disabled: false,
isWorking: false,
onClick: () => {
},
};
export default Button;