Refactor styling of multiple screens and NavBar

-  Add the title to the navbar
- Add react-helmet and Title component to help change tab/page title
- Improve layout of Schedule screen
- Add a "clear selection" button to Schedule screen
- Improve readability of forms
This commit is contained in:
rui hildt 2020-06-03 01:11:34 +02:00
parent a97f8dc1c3
commit 27e0889737
13 changed files with 297 additions and 152 deletions

View File

@ -19,6 +19,7 @@
"react": "^16.13.1",
"react-app-rewired": "^2.1.6",
"react-dom": "^16.13.1",
"react-helmet": "^6.0.0",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"rsuite": "^4.5.0"

View File

@ -9,10 +9,10 @@ export default function TimezonePicker() {
<InputPicker
data={timezones}
// cleanable={false}
style={{ width: 320 }}
style={{ width: "100%" }}
labelKey='timezone'
groupBy='area'
placeholder='Type to search and select your timezone'
placeholder='type to search your timezone'
onSelect={(value, item, event) => setTimezone(item.timezone)}
onClean={(event) => setTimezone('')}
value={timezone}

View File

@ -0,0 +1,11 @@
import React from 'react';
import { Helmet } from 'react-helmet';
export default function TitleComponent({ title }) {
var defaultTitle = 'Meeting Planner';
return (
<Helmet>
<title>{title ? `${title} | Meeting Planner` : defaultTitle}</title>
</Helmet>
);
}

View File

@ -1,17 +1,28 @@
import React from 'react';
import { Header, Navbar } from 'rsuite';
import { Header, Navbar, Nav } from 'rsuite';
import MenuDropdown from './MenuDropdown';
import Title from './../General/Title';
const headerStyle = {
borderRadius: '7px 7px 0 0',
};
export default function NavBar() {
export default function NavBar({ title }) {
return (
<Header>
<Navbar appearance='inverse' style={headerStyle}>
<Navbar.Body>
<Title title={title} />
<Navbar appearance='inverse' >
<Navbar.Body
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<Nav style={{ visibility: 'hidden' }}>
{/* This hidden nav is a hack to have the title perfectly centered. */}
<MenuDropdown />
</Nav>
<div>
<h3>{title}</h3>
</div>
<MenuDropdown />
</Navbar.Body>
</Navbar>

View File

@ -3,13 +3,17 @@ import FullCalendar from '@fullcalendar/react';
import dayGridPlugin from '@fullcalendar/daygrid';
import interaction from '@fullcalendar/interaction';
import './DaySelector.less';
export default function DaySelector({ eventsList, handleSelect, handleClear }) {
return (
<FullCalendar
initialView='dayGridMonth'
plugins={[dayGridPlugin, interaction]}
initialView='dayGridMonth'
// showNonCurrentDates={false}
selectable={true}
unselectAuto={false}
// unselectAuto={false}
longPressDelay={150}
select={(info) => handleSelect(info)}
defaultAllDay={true}
events={eventsList}

View File

@ -0,0 +1,3 @@
.fc-view-harness {
padding-bottom: 100% !important
}

View File

@ -5,7 +5,6 @@ import { Divider, Icon, IconButton } from 'rsuite';
export default function SelectedDates({ datesList, handleDelete }) {
return (
<>
<h3>Dates selected</h3>
<ul>
{datesList.map((date) => (
<li key={date}>

View File

@ -3,27 +3,33 @@ import 'rsuite/lib/styles/index.less';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Dashboard from './Dashboard';
import Schedule from './Schedule';
import Schedule from './Schedule/Schedule';
import Login from './Login';
import Register from './Register';
import NavBar from '../components/Navbar/NavBar';
const titles = {
schedule: 'Schedule a meeting',
dashboard: 'Dashboard',
login: 'Login',
register: 'Registration',
settings: 'Settings'
};
export default function App() {
return (
<Router>
<NavBar />
<Switch>
<Route path='/' exact>
<Schedule />
<Schedule title={titles.schedule} />
</Route>
<Route path='/dashboard'>
<Dashboard />
<Dashboard title={titles.dashboard}/>
</Route>
<Route path='/login'>
<Login />
<Login title={titles.login}/>
</Route>
<Route path='/register'>
<Register />
<Register title={titles.register} />
</Route>
</Switch>
</Router>

View File

@ -1,14 +1,10 @@
import React from 'react';
import {
FlexboxGrid,
Form,
FormGroup,
FormControl,
// HelpBlock,
Button,
} from 'rsuite';
import { Panel, Form, FormGroup, FormControl, ControlLabel, HelpBlock, Button } from 'rsuite';
import NavBar from './../components/Navbar/NavBar';
// TODO Move to a .less file
const boxStyle = {
maxWidth: 373,
margin: '0 auto',
@ -19,37 +15,34 @@ const boxStyle = {
padding: '1rem',
};
const h3Style = { margin: '1rem' };
export default function Login() {
export default function Login({ title }) {
return (
<FlexboxGrid justify='center' style={boxStyle}>
<h3 style={h3Style}>Login</h3>
<Form horizontal>
<FormGroup>
<FormControl
name='email'
type='email'
placeholder='email'
/>
</FormGroup>
<FormGroup>
<FormControl
name='password'
type='password'
placeholder='password'
/>
{/* <HelpBlock tooltip>
Minimum password length is 8 characters
</HelpBlock> */}
</FormGroup>
<FormGroup>
<Button appearance='primary' block>
Sign in
</Button>
</FormGroup>
<Button appearance='link'>Forgot password?</Button>
</Form>
</FlexboxGrid>
<>
<NavBar title={title} />
<Panel bordered style={boxStyle}>
<Form>
<FormGroup>
<ControlLabel>Email</ControlLabel>
<FormControl
name='email'
type='email'
/>
</FormGroup>
<ControlLabel>Password</ControlLabel>
<FormGroup>
<FormControl
name='password'
type='password'
/>
</FormGroup>
<FormGroup>
<Button appearance='primary' block size="lg">
Sign in
</Button>
</FormGroup>
<Button appearance='link'>Forgot password?</Button>
</Form>
</Panel>
</>
);
}

View File

@ -1,16 +1,11 @@
import React from 'react';
import {
FlexboxGrid,
Form,
FormGroup,
FormControl,
HelpBlock,
Button,
} from 'rsuite';
import { Form, FormGroup, FormControl, ControlLabel, HelpBlock, Button, Panel } from 'rsuite';
import NavBar from './../components/Navbar/NavBar';
import TimezonePicker from '../components/General/TimezonePicker';
// TODO Move to a .less file
const boxStyle = {
maxWidth: 373,
margin: '0 auto',
@ -21,46 +16,47 @@ const boxStyle = {
padding: '1rem',
};
const h3Style = { margin: '1rem' };
export default function Register() {
export default function Register({ title }) {
return (
<FlexboxGrid justify='center' style={boxStyle}>
<h3 style={h3Style}>Register</h3>
<Form horizontal>
<FormGroup>
<FormControl
name='username'
type='text'
placeholder='username'
/>
</FormGroup>
<FormGroup>
<FormControl
name='email'
type='email'
placeholder='email'
/>
</FormGroup>
<FormGroup>
<TimezonePicker />
</FormGroup>
<FormGroup>
<FormControl
name='password'
type='password'
placeholder='password'
/>
<HelpBlock tooltip>
Minimum password length is 8 characters
</HelpBlock>
</FormGroup>
<FormGroup>
<Button appearance='primary' block>
Register
</Button>
</FormGroup>
</Form>
</FlexboxGrid>
<>
<NavBar title={title} />
<Panel bordered style={boxStyle}>
<Form>
<FormGroup>
<ControlLabel>Username</ControlLabel>
<FormControl
name='username'
type='text'
/>
</FormGroup>
<FormGroup>
<ControlLabel>Email</ControlLabel>
<FormControl
name='email'
type='email'
/>
</FormGroup>
<ControlLabel>Timezone</ControlLabel>
<FormGroup>
<TimezonePicker />
</FormGroup>
<ControlLabel>Password</ControlLabel>
<FormGroup>
<FormControl
name='password'
type='password'
/>
<HelpBlock>
Minimum password length is 8 characters
</HelpBlock>
</FormGroup>
<FormGroup>
<Button appearance='primary' block size="lg">
Register
</Button>
</FormGroup>
</Form>
</Panel>
</>
);
}

View File

@ -1,18 +1,31 @@
import React, { useState, useEffect } from 'react';
import { Container, Form, FormControl, FormGroup, Input, Button } from 'rsuite';
import {
Panel,
Form,
FormControl,
ControlLabel,
Input,
HelpBlock,
Button,
ButtonGroup,
Message,
FormGroup,
} from 'rsuite';
import { DateTime } from 'luxon';
import TimezonePicker from '../components/General/TimezonePicker';
import DaySelector from '../components/Schedule/DaySelector';
import DurationSelector from '../components/Schedule/DurationSelector';
import SelectedDates from '../components/Schedule/SelectedDates';
import NavBar from '../../components/Navbar/NavBar';
import TimezonePicker from '../../components/General/TimezonePicker';
import DaySelector from '../../components/Schedule/DaySelector';
import DurationSelector from '../../components/Schedule/DurationSelector';
import SelectedDates from '../../components/Schedule/SelectedDates';
export default function Schedule() {
import './Schedule.less';
export default function Schedule({ title }) {
const [eventsList, setEventsList] = useState([]);
const [datesList, setDatesList] = useState(eventsToDates(eventsList));
useEffect(() => {
// Update selected dates
let updatedDates = eventsToDates(eventsList).sort();
setDatesList(updatedDates);
}, [eventsList]);
@ -104,45 +117,80 @@ export default function Schedule() {
};
return (
<Container>
<h3>Schedule a meeting</h3>
<Form>
<FormGroup>
<FormControl name='title' type='text' placeholder='title' />
</FormGroup>
<FormGroup>
<Input
name='description'
componentClass='textarea'
type='text'
rows={3}
placeholder='Description'
/>
</FormGroup>
<FormGroup>
<TimezonePicker />
</FormGroup>
<FormGroup style={{ width: 200 }}>
<DurationSelector />
</FormGroup>
<FormGroup>
<DaySelector
eventsList={eventsList}
handleSelect={handleSelect}
handleClear={handleClear}
/>
</FormGroup>
{datesList.length > 0 && (
<FormGroup>
<SelectedDates
datesList={datesList}
handleDelete={handleDelete}
<>
<NavBar title={title} />
<Panel style={containerStyle}>
<Form className={'meeting-container'}>
<div className={'meeting-info'}>
<FormGroup>
<ControlLabel>Title</ControlLabel>
<FormControl name='title' type='text' />
<HelpBlock>This field is required</HelpBlock>
</FormGroup>
<FormGroup>
<ControlLabel>Description</ControlLabel>
<Input
name='description'
componentClass='textarea'
type='text'
rows={3}
placeholder='(optional)'
/>
</FormGroup>
<div className='meeting-options-inline'>
<FormGroup className='meeting-timezone'>
<ControlLabel>Timezone</ControlLabel>
<TimezonePicker />
</FormGroup>
<FormGroup className='meeting-duration'>
<ControlLabel>Duration</ControlLabel>
<DurationSelector />
</FormGroup>
</div>
<ButtonGroup justified>
<Button
appearance='ghost'
block
size='lg'
disabled={datesList.length === 0}
onClick={() => handleClear()}
>
Clear selection
</Button>
<Button
appearance='primary'
size='lg'
block
disabled={datesList.length === 0}
>
Confirm dates
</Button>
</ButtonGroup>
<div className={'selected-dates'}></div>
{datesList.length > 0 && (
<>
<SelectedDates
datesList={datesList}
handleDelete={handleDelete}
/>
</>
)}
</div>
<div className={'day-selector'}>
<Message
showIcon
type='info'
description='Select possible meetings dates on the calendar.'
/>
<Button>Confirm dates</Button>
</FormGroup>
)}
</Form>
</Container>
<DaySelector
eventsList={eventsList}
handleSelect={handleSelect}
handleClear={handleClear}
/>
</div>
</Form>
</Panel>
</>
);
}
@ -154,3 +202,10 @@ const eventsToDates = (events) => {
});
return dates;
};
const containerStyle = {
// TODO Move to a .less file
maxWidth: '1200px',
margin: '30px auto',
backgroundColor: 'rgba(255,255,255,0.6)',
};

View File

@ -0,0 +1,46 @@
.meeting-container {
display: grid;
grid-template-columns: 6fr 5fr;
grid-template-rows: 1fr;
grid-template-areas:
'selector info';
column-gap: 2em;
row-gap: 2em;
grid-column-gap: 2em;
grid-row-gap: 2em;
}
.meeting-info {
grid-area: info;
margin: 1em 0 3em 0;
}
.day-selector {
grid-area: selector;
margin: 1em 0 3em 0;
}
.selected-dates {
grid-area: selected;
ul {
column-count: 2;
}
}
.meeting-options-inline {
display: flex;
justify-content: space-between;
.meeting-timezone {
width: 65%
}
.meeting-duration {
width:30%
}
}
// Fix button alignment bug
.rs-btn-block + .rs-btn-block {
margin-top: unset;
}

View File

@ -8795,6 +8795,21 @@ react-error-overlay@^6.0.7:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==
react-fast-compare@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
react-helmet@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.0.0.tgz#fcb93ebaca3ba562a686eb2f1f9d46093d83b5f8"
integrity sha512-My6S4sa0uHN/IuVUn0HFmasW5xj9clTkB9qmMngscVycQ5vVG51Qp44BEvLJ4lixupTwDlU9qX1/sCrMN4AEPg==
dependencies:
object-assign "^4.1.1"
prop-types "^15.7.2"
react-fast-compare "^2.0.4"
react-side-effect "^2.1.0"
react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -8894,6 +8909,11 @@ react-scripts@3.4.1:
optionalDependencies:
fsevents "2.1.2"
react-side-effect@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.0.tgz#1ce4a8b4445168c487ed24dab886421f74d380d3"
integrity sha512-IgmcegOSi5SNX+2Snh1vqmF0Vg/CbkycU9XZbOHJlZ6kMzTmi3yc254oB1WCkgA7OQtIAoLmcSFuHTc/tlcqXg==
react-virtualized@^9.21.0:
version "9.21.2"
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.21.2.tgz#02e6df65c1e020c8dbf574ec4ce971652afca84e"