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": "^16.13.1",
"react-app-rewired": "^2.1.6", "react-app-rewired": "^2.1.6",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-helmet": "^6.0.0",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "3.4.1", "react-scripts": "3.4.1",
"rsuite": "^4.5.0" "rsuite": "^4.5.0"

View File

@ -9,10 +9,10 @@ export default function TimezonePicker() {
<InputPicker <InputPicker
data={timezones} data={timezones}
// cleanable={false} // cleanable={false}
style={{ width: 320 }} style={{ width: "100%" }}
labelKey='timezone' labelKey='timezone'
groupBy='area' groupBy='area'
placeholder='Type to search and select your timezone' placeholder='type to search your timezone'
onSelect={(value, item, event) => setTimezone(item.timezone)} onSelect={(value, item, event) => setTimezone(item.timezone)}
onClean={(event) => setTimezone('')} onClean={(event) => setTimezone('')}
value={timezone} 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 React from 'react';
import { Header, Navbar } from 'rsuite'; import { Header, Navbar, Nav } from 'rsuite';
import MenuDropdown from './MenuDropdown'; import MenuDropdown from './MenuDropdown';
import Title from './../General/Title';
const headerStyle = { export default function NavBar({ title }) {
borderRadius: '7px 7px 0 0',
};
export default function NavBar() {
return ( return (
<Header> <Header>
<Navbar appearance='inverse' style={headerStyle}> <Title title={title} />
<Navbar.Body> <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 /> <MenuDropdown />
</Navbar.Body> </Navbar.Body>
</Navbar> </Navbar>

View File

@ -3,13 +3,17 @@ import FullCalendar from '@fullcalendar/react';
import dayGridPlugin from '@fullcalendar/daygrid'; import dayGridPlugin from '@fullcalendar/daygrid';
import interaction from '@fullcalendar/interaction'; import interaction from '@fullcalendar/interaction';
import './DaySelector.less';
export default function DaySelector({ eventsList, handleSelect, handleClear }) { export default function DaySelector({ eventsList, handleSelect, handleClear }) {
return ( return (
<FullCalendar <FullCalendar
initialView='dayGridMonth'
plugins={[dayGridPlugin, interaction]} plugins={[dayGridPlugin, interaction]}
initialView='dayGridMonth'
// showNonCurrentDates={false}
selectable={true} selectable={true}
unselectAuto={false} // unselectAuto={false}
longPressDelay={150}
select={(info) => handleSelect(info)} select={(info) => handleSelect(info)}
defaultAllDay={true} defaultAllDay={true}
events={eventsList} 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 }) { export default function SelectedDates({ datesList, handleDelete }) {
return ( return (
<> <>
<h3>Dates selected</h3>
<ul> <ul>
{datesList.map((date) => ( {datesList.map((date) => (
<li key={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 { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Dashboard from './Dashboard'; import Dashboard from './Dashboard';
import Schedule from './Schedule'; import Schedule from './Schedule/Schedule';
import Login from './Login'; import Login from './Login';
import Register from './Register'; 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() { export default function App() {
return ( return (
<Router> <Router>
<NavBar />
<Switch> <Switch>
<Route path='/' exact> <Route path='/' exact>
<Schedule /> <Schedule title={titles.schedule} />
</Route> </Route>
<Route path='/dashboard'> <Route path='/dashboard'>
<Dashboard /> <Dashboard title={titles.dashboard}/>
</Route> </Route>
<Route path='/login'> <Route path='/login'>
<Login /> <Login title={titles.login}/>
</Route> </Route>
<Route path='/register'> <Route path='/register'>
<Register /> <Register title={titles.register} />
</Route> </Route>
</Switch> </Switch>
</Router> </Router>

View File

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

View File

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

View File

@ -1,18 +1,31 @@
import React, { useState, useEffect } from 'react'; 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 { DateTime } from 'luxon';
import TimezonePicker from '../components/General/TimezonePicker'; import NavBar from '../../components/Navbar/NavBar';
import DaySelector from '../components/Schedule/DaySelector'; import TimezonePicker from '../../components/General/TimezonePicker';
import DurationSelector from '../components/Schedule/DurationSelector'; import DaySelector from '../../components/Schedule/DaySelector';
import SelectedDates from '../components/Schedule/SelectedDates'; 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 [eventsList, setEventsList] = useState([]);
const [datesList, setDatesList] = useState(eventsToDates(eventsList)); const [datesList, setDatesList] = useState(eventsToDates(eventsList));
useEffect(() => { useEffect(() => {
// Update selected dates
let updatedDates = eventsToDates(eventsList).sort(); let updatedDates = eventsToDates(eventsList).sort();
setDatesList(updatedDates); setDatesList(updatedDates);
}, [eventsList]); }, [eventsList]);
@ -104,45 +117,80 @@ export default function Schedule() {
}; };
return ( return (
<Container> <>
<h3>Schedule a meeting</h3> <NavBar title={title} />
<Form> <Panel style={containerStyle}>
<FormGroup> <Form className={'meeting-container'}>
<FormControl name='title' type='text' placeholder='title' /> <div className={'meeting-info'}>
</FormGroup> <FormGroup>
<FormGroup> <ControlLabel>Title</ControlLabel>
<Input <FormControl name='title' type='text' />
name='description' <HelpBlock>This field is required</HelpBlock>
componentClass='textarea' </FormGroup>
type='text' <FormGroup>
rows={3} <ControlLabel>Description</ControlLabel>
placeholder='Description' <Input
/> name='description'
</FormGroup> componentClass='textarea'
<FormGroup> type='text'
<TimezonePicker /> rows={3}
</FormGroup> placeholder='(optional)'
<FormGroup style={{ width: 200 }}> />
<DurationSelector /> </FormGroup>
</FormGroup> <div className='meeting-options-inline'>
<FormGroup> <FormGroup className='meeting-timezone'>
<DaySelector <ControlLabel>Timezone</ControlLabel>
eventsList={eventsList} <TimezonePicker />
handleSelect={handleSelect} </FormGroup>
handleClear={handleClear} <FormGroup className='meeting-duration'>
/> <ControlLabel>Duration</ControlLabel>
</FormGroup> <DurationSelector />
{datesList.length > 0 && ( </FormGroup>
<FormGroup> </div>
<SelectedDates <ButtonGroup justified>
datesList={datesList} <Button
handleDelete={handleDelete} 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> <DaySelector
</FormGroup> eventsList={eventsList}
)} handleSelect={handleSelect}
</Form> handleClear={handleClear}
</Container> />
</div>
</Form>
</Panel>
</>
); );
} }
@ -154,3 +202,10 @@ const eventsToDates = (events) => {
}); });
return dates; 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" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA== 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: 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" version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -8894,6 +8909,11 @@ react-scripts@3.4.1:
optionalDependencies: optionalDependencies:
fsevents "2.1.2" 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: react-virtualized@^9.21.0:
version "9.21.2" version "9.21.2"
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.21.2.tgz#02e6df65c1e020c8dbf574ec4ce971652afca84e" resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.21.2.tgz#02e6df65c1e020c8dbf574ec4ce971652afca84e"