How to make a Drawer like in Google Inbox with Material-UI - reactjs

I'm building an app with Meteor, React and Material-UI. I have a custom left nav component based on Drawer.
class LeftNav extends Component {
constructor(props){
super(props);
}
render() {
return (
<Drawer open={this.props.open}
docked={false}
onRequestChange={this.props.handleOnRequestChange}
>
<Card >
<CardMedia overlay={<div><Avatar src='avatar.png' size={50} style={styles.avatar} /> <CardTitle title="Phil Cruz" subtitle="phil#philcruz.com" titleColor={darkWhite} subtitleColor={lightWhite}/></div>} >
<img src="/left_nav_wallpaper.jpg" />
</CardMedia>
</Card>
<MenuItem primaryText="testdomain1.com" leftIcon={<ActionGrade />} />
<MenuItem primaryText="testdomain2.com" leftIcon={<ActionHttp />}/>
<MenuItem primaryText="Add site..." leftIcon={<ContentAdd />} />
<Divider />
<MenuItem primaryText="Settings" leftIcon={<Settings />} />
<MenuItem primaryText="Help & About" leftIcon={<HelpOutline />} />
</Drawer>
);
}
};
I want to make it so it behaves like the Google InBox left nav/drawer. It has 3 sections: The top with the cover image and avatar, middle section and the bottom section.
I want the same behavior where:
the bottom menu items are fixed to the bottom (in red in the below
image)
the top section can scroll
How can I do that?

By reading these questions/answers:
How to create a sticky footer inside the LeftNav?
Get viewport/window height in ReactJS
Reactjs - Rerender on browser resize
I was able to come up with a solution. Basically, you need to put the content in 2 divs. Use absolute positioning in the bottom div to make it fixed to the bottom. You then need to calculate the height of the top div based on the height of the window minus the height of the footer div. Listen to the resize window event so you can update the div height manually as the browser is resized.
Here's the code:
class LeftNav extends Component {
constructor(props){
super(props);
this.state = { };
this.updateDimensions = this.updateDimensions.bind(this);
}
componentDidMount(){
this.updateDimensions();
window.addEventListener("resize", this.updateDimensions);
}
updateDimensions() {
this.setState({
height: window.innerHeight,
footerHeight: document.getElementById('leftNavFooter').clientHeight
});
}
render() {
return (
<Drawer open={this.props.open}
docked={false}
onRequestChange={this.props.handleOnRequestChange}
>
<div style={{overflowY: 'auto', overflowX: 'hidden', height: (this.state.height - this.state.footerHeight) + 'px'}}>
<Card >
<CardMedia overlay={<div><Avatar src='avatar.png' size={50} style={{ marginLeft: 16 }} /> <CardTitle title="Phil Cruz" subtitle="phil#philcruz.com" titleColor={darkWhite} subtitleColor={lightWhite}/></div>} >
<img src="/left_nav_wallpaper.jpg" />
</CardMedia>
</Card>
<Menu>
<MenuItem primaryText="testdomain1.com" leftIcon={<ActionGrade />} />
<MenuItem primaryText="testdomain2.com" leftIcon={<ActionHttp />}/>
<MenuItem primaryText="Add site..." leftIcon={<ContentAdd />} />
</Menu>
</div>
<div id="leftNavFooter" style={{position: 'absolute', bottom: 0, width: '100%', overflow: 'hidden'}}>
<Divider />
<Menu>
<MenuItem primaryText="Settings" leftIcon={<Settings />} />
<MenuItem primaryText="Help & About" leftIcon={<HelpOutline />} />
</Menu>
</div>
</Drawer>
);
}
};

Related

Nextjs Thumbs is not showing in react-responsive-carousel while using next image

I am using react-responsive-carousel to show the image gallery of products with thumbs, while I am using a simple img HTML element the thums show up but while I use next image the thumbs vanish, I wonder how can I fix this issue.
<Carousel showThumbs>
{images.map((image) => (
<Box>
<Badge
badgeContent="30%"
color="primary"
anchorOrigin={{
vertical: 'top',
horizontal: 'left',
}}
sx={{
position: 'absolute',
top: '2rem',
left: '2.5rem',
}}
/>
{/* <img src={image.original} alt="ok" /> this word just fine*/}
{/* this does not show up thumbs */}
<Image
src={image.original}
alt="piece"
width={image.originalWidth}
height={image.originalHeight}
/>
</Box>
))}
</Carousel>
It's because react-responsive-carousel can't get image list inside custom components, it can only get from tag or tag inside tag. I think nextjs 's Image components count as custom components too.
ref: https://github.com/leandrowd/react-responsive-carousel/blob/master/TROUBLESHOOTING.md
When I implement Slider with SwipeableViews, I used pure image like this.
package.json
"dependencies": {
...
"react-swipeable-views": "^0.14.0",
"react-swipeable-views-utils": "^0.14.0",
...
}
slider-carousel-test.js
import SwipeableViews from 'react-swipeable-views';
import { autoPlay } from 'react-swipeable-views-utils';
const AutoPlaySwipeableViews = autoPlay(SwipeableViews);
export default function TestComponent (){
return (
<AutoPlaySwipeableViews
axis='x'
index={activeStep}//for auto
onChangeIndex={handleStepChange}//for user click
enableMouseEvents
>
{
data.map((elem, idx)=>
<Box
component="img"
sx={{
display:'block',
overflow:'hidden',
width:'100%',
}}
src={`https://contents.herokuapp.com/images/content${1+idx}.png`}
alt="No Image"
/>
);
}
</AutoPlaySwipeableViews>
)
}
Because Image from next/image does not provide image for react-responsive-carousel, I recommend you this way...
You need to customize the renderThumbs method.
renderThumbs = {() => (
images.map((image, index) => (
<Image
key={index}
src={image.original}
alt="piece"
width={image.originalWidth}
height={image.originalHeight}
/>
)))}

React: How to render components based on MenuItem clicked?

Below is the code:
import React from 'react';
import { Layout, Menu, Breadcrumb } from 'antd';
import { UserOutlined, ProfileFilled, CreditCardFilled, SearchOutlined, BankOutlined} from '#ant-design/icons';
const { SubMenu } = Menu;
const { Header, Content, Sider, Footer } = Layout;
function Admin(){
function handleClick(){
console.log("Search")
}
return(
<Layout>
<Header className="header">
<div className="logo" />
<Menu theme="light" mode="horizontal" defaultSelectedKeys={['2']} icon={<UserOutlined />}>
<Menu.Item key="1">Creame Cookies</Menu.Item>
</Menu>
</Header>
<Layout>
<Sider width={200} className="site-layout-background">
<Menu
theme="dark"
mode="inline"
defaultOpenKeys={['Dashboard']}
defaultSelectedKeys={['Dashboard']}
style={{ height: '250%', borderRight: 0, width: 250 }}
>
<Menu.Item key="Dashboard" icon={<UserOutlined />}>
Dashboard
</Menu.Item>
<Menu.Item onClick={handleClick} key="Search" icon={<SearchOutlined />}>
Search
</Menu.Item>
<SubMenu key="Profiles" icon={<ProfileFilled />} title="Profiles">
<Menu.Item key="1">Free Profiles</Menu.Item>
<Menu.Item key="2">Premium Profiles</Menu.Item>
</SubMenu>
<Menu.Item key="Payments" icon={<CreditCardFilled />}>
Payments
</Menu.Item>
</Menu>
</Sider>
<Layout style={{ padding: '0 72px 72px' }}>
<Content
className="site-layout-background"
style={{
padding: 24,
margin: 0,
minHeight: 500,
}}
>
<h1>Default</h1>
</Content>
</Layout>
</Layout>
{/*<Footer style={{ textAlign: 'center' }}>Creame Cookies Ltd</Footer> */}
</Layout>
)
}
export default Admin
Now, I need to render components at the location of the h1 tag with text as Default, based on the menuitem clicked. I am unsure of how to do this in react since I am new to it and feeling it difficult as compared to vannila javascript and jQuery in which I have already worked.
<h1>{someCondition ? 'Text A': 'Text B'}</h1> is generally how you handle this. You can always do the condition at the top of the component before your return statement if you prefer.
{ } is syntax to wrap an expression in React. You could put a ternary or just a variable in there to render.
Could also do <h1>{someCondtion ? <ComponentA /> : <ComponentB />}</h1>.

Sticky content scrolling up into fixed menu header Semantic UI

So I've set up some sticky rail content using an example for the SUIR docs. However, when scrolling the main content, the rail content scrolls into my fixed header.
Do I need to set more rules on the style prop for <Rail>?
Codesandbox link here
To view the issue, navigate to Docs from the menu bar.
The <Rail> component is at the bottom of Overview.js file
export default class Overview extends Component {
contextRef = createRef();
render() {
return (
<div>
<FixedMenuLayout />
<Grid centered columns={3} style={{ marginTop: "7em" }}>
<Grid.Column>
<Ref innerRef={this.contextRef}>
<Segment>
<Docs />
<Rail position="left">
<Sticky context={this.contextRef}>
<Header as="h3">Getting Started</Header>
<List style={{ padding: "5px" }}>
<List.Item>Overview</List.Item>
<List.Item>Examples</List.Item>
<List.Item>Tutorial</List.Item>
</List>
<Header as="h3">How To</Header>
<List style={{ padding: "5px" }}>
<List.Item>Building</List.Item>
<List.Item>Standalone</List.Item>
<List.Item>Deployment</List.Item>
</List>
<Header as="h3">Development</Header>
<List style={{ padding: "5px" }}>
<List.Item>Road Map</List.Item>
<List.Item>Upcoming Features</List.Item>
</List>
</Sticky>
</Rail>
<Rail position="right">
<Sticky context={this.contextRef}>
<Header as="h3">Overview</Header>
<List style={{ padding: "5px" }}>
<List.Item>Overview</List.Item>
<List.Item>Examples</List.Item>
<List.Item>Tutorial</List.Item>
</List>
</Sticky>
</Rail>
</Segment>
</Ref>
</Grid.Column>
</Grid>
</div>
);
}
}
Well a rather easy fix just didn't see the correct prop that handles this exact issue.
The offset prop for <Sticky> needs to be set to an appropriate value.
In my case,
<Sticky offset={90} context={this.contextRef}>

How to change Content based on Menu Item click in AntD (React UI Library)

I am following AntD Menu tutorial from here and it shows how to use this component. But I am failed to understand that how to change my Content when I click on different menu option. Tried searching a lot but did not find any useful help.
import { Layout, Menu, Icon } from 'antd';
const { Header, Sider, Content } = Layout;
class SiderDemo extends React.Component {
state = {
collapsed: false,
};
toggle = () => {
this.setState({
collapsed: !this.state.collapsed,
});
}
render() {
return (
<Layout>
<Sider
trigger={null}
collapsible
collapsed={this.state.collapsed}
>
<div className="logo" />
<Menu theme="dark" mode="inline" defaultSelectedKeys={['1']}>
<Menu.Item key="1">
<Icon type="user" />
<span>nav 1</span>
</Menu.Item>
<Menu.Item key="2">
<Icon type="video-camera" />
<span>nav 2</span>
</Menu.Item>
<Menu.Item key="3">
<Icon type="upload" />
<span>nav 3</span>
</Menu.Item>
</Menu>
</Sider>
<Layout>
<Header style={{ background: '#fff', padding: 0 }}>
<Icon
className="trigger"
type={this.state.collapsed ? 'menu-unfold' : 'menu-fold'}
onClick={this.toggle}
/>
</Header>
<Content style={{ margin: '24px 16px', padding: 24, background: '#fff', minHeight: 280 }}>
Some Conent
</Content>
</Layout>
</Layout>
);
}
}
ReactDOM.render(<SiderDemo />, mountNode);
Can you pls help.
Just WRAP your Layout tag inside Router tag of react-router-dom.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import { Layout, Menu, Icon } from 'antd';
import Dashboard from './containers/Dashboard/Dashboard';
import Meseros from './containers/Meseros/Meseros';
const { Header, Content, Footer, Sider } = Layout;
const SubMenu = Menu.SubMenu;
class RouterApp extends Component {
state = {
collapsed: false,
};
onCollapse = (collapsed) => {
this.setState({ collapsed });
}
toggle = () => {
this.setState({
collapsed: !this.state.collapsed,
});
}
render() {
return (
<Router>
<Layout style={{ minHeight: '100vh' }}>
<Sider
collapsible
collapsed={this.state.collapsed}
onCollapse={this.onCollapse}>
<div className="logo" />
<Menu theme="dark" defaultSelectedKeys={['1']} mode="inline">
<Menu.Item key="1">
<Icon type="pie-chart" />
<span>Deshboard</span>
<Link to="/" />
</Menu.Item>
<Menu.Item key="2">
<Icon type="desktop" />
<span>Meseros</span>
<Link to="/meseros" />
</Menu.Item>
</Menu>
</Sider>
<Layout>
<Header style={{ background: '#fff', padding: 0, paddingLeft: 16 }}>
<Icon
className="trigger"
type={this.state.collapsed ? 'menu-unfold' : 'menu-fold'}
style={{ cursor: 'pointer' }}
onClick={this.toggle}
/>
</Header>
<Content style={{ margin: '24px 16px', padding: 24, background: '#fff', minHeight: 280 }}>
<Route exact path="/" component={Dashboard} />
<Route path="/meseros" component={Meseros} />
</Content>
</Layout>
</Layout>
</Router>
);
}
}
export default RouterApp;
If you don't want to change the route every time you choose a menu item you can use the following approach
function MyComponent(){
const [selectedMenuItem, setSelectedMenuItem]= useState('item1');
const componentsSwtich = (key) => {
switch (key) {
case 'item1':
return (<h1>item1</h1>);
case 'item2':
return (<h1>item2</h1>);
case 'item3':
return (<h3>item3/h3>);
default:
break;
}
};
return(
<div>
<Menu selectedKeys={selectedMenuItem} mode="horizontal" onClick={(e) =>
setSelectedMenuItem(e.key)}>
<Menu.Item key="item1">your first component here</Menu.Item>
<Menu.Item key="item2">your second here</Menu.Item>
<Menu.Item key="item3">your third here</Menu.Item>
</Menu>
<div>
{componentsSwitch(selectedMenuItem)}
</div>
</div>)
You can bind onClick with your MenuItem and re render the component upon clicking any menuItem. And you can make you content in your Constant.js or initialize it in your component state.
Add onClick method
<Menu.Item
key="1"
onClick={this.handleMenuClick}
>
<Icon type="user" />
<span>nav 1</span>
</Menu.Item>
make a handle for MenuItem click:
handleMenuClick = event => {
//you can get here event.target.value
//filter the content
//setState the content your component will re render and content will be updated.
}
In case you want to change the route, there is a working example here
https://codesandbox.io/embed/fervent-thunder-egyk1?fontsize=14&hidenavigation=1&theme=dark
Add Link component in Menu.Item and wrap with BrowserRouter from react-router-dom
<Menu.Item key="2">
CI/CD Pipelines
<Link to={`${match.url}/cicd`} />
</Menu.Item>
You can add the onClick prop for the Menu item as follow:
NB. I am using NextJs router to link the menus to their own corresponding page. In case you are using React, use Link from router-dom.
<pre>
<Menu
onClick={({ keyPath }) => router.push(`/${keyPath}`)}
mode="inline"
defaultSelectedKeys={["dashboard"]}
defaultOpenKeys={["company-info"]}
items={navs}
theme="dark"
/>

Semantic-UI-react fixed sidebar

Have Googled, searched within semantic ui's docs and issues page, and searched within stackoverflow. Couldn't find the answer.
Within Semantic-ui-react, how do I make a sidebar whose content is fixed to the screen? What I currently have is this:
<Sidebar.Pushable as={Segment}>
<Sidebar
id="sidebar"
as={Menu}
animation="overlay"
direction="right"
visible={this.state.visible}
vertical
inverted
>
{this.getMenuItems()}
</Sidebar>
<Sidebar.Pusher>
<Route path="/" component={Filler} />
</Sidebar.Pusher>
</Sidebar.Pushable>
There doesn't seem to be any word in it in the semantic-ui-react documentation, and making Sidebar.Pushable, Sidebar, or any of the Menu Items position:fixed; doesn't seem to work either.
I was able to achieve a sticky sidebar with the help of this answer.
Basically, it states that in order to have a fixed sidebar that sticks to the our infinite scrolling page, we must remove the transform attribute
on the parent container. The reasoning is because the transform changes the positioning context from the viewport to the
rotated element. As a result, the "fixed" child element, behaves as if it has "absolute" positioning.
I added this to the sidebar.overrides file
/* Page Context */
.pushable:not(body) {
transform: none;
}
.pushable:not(body) > .ui.sidebar,
.pushable:not(body) > .fixed,
.pushable:not(body) > .pusher:after {
position: fixed;
}
This solution is meant for the base semantic-ui library. Since semantic-ui-react requires semantic-ui, this ends up working for semantic-ui-react sidebars as well.
Give a try with below code.
<Sidebar as={Menu} animation='overlay' icon='labeled' inverted vertical visible width='wide'>
<Menu.Item as={Link} to="/admin">
<Icon name='building' />
Rubykraft
</Menu.Item>
<Menu.Item as='a'>
<Icon name='user' />
Shan
</Menu.Item>
<Menu.Item as='a'>
<Icon name='user' />
Vishnu
</Menu.Item>
</Sidebar>
I've used classes from semantic-ui's Sidebar module to create the desired fixed sidebar. If you want a more Component(ish) code, you should replace the pusher class with it's correspondent Sidebar.Pusher Component.
Here's my code:
import React, { Component } from 'react'
import { Dropdown, Icon, Input, Menu } from 'semantic-ui-react'
export default class MySidebar extends Component {
state = {}
handleItemClick = (e, { name }) => this.setState({ activeItem: name })
componentDidMount() {}
render() {
const { activeItem } = this.state
return(
<div className='pusher'>
<div className='full height'>
<div className='toc'>
<Menu className='inverted vertical left fixed'>
<Menu.Item>
Home
<Icon name='dashboard' />
<Menu.Menu>
<Menu.Item name='search' active={activeItem === 'search'} onClick={this.handleItemClick}>
Search
</Menu.Item>
<Menu.Item name='add' active={activeItem === 'add'} onClick={this.handleItemClick}>
Add
</Menu.Item>
<Menu.Item name='about' active={activeItem === 'about'} onClick={this.handleItemClick}>
Remove
</Menu.Item>
</Menu.Menu>
</Menu.Item>
<Menu.Item name='browse' active={activeItem === 'browse'} onClick={this.handleItemClick}>
<Icon name='grid layout' />
Browse
</Menu.Item>
<Menu.Item name='messages' active={activeItem === 'messages'} onClick={this.handleItemClick}>
Messages
</Menu.Item>
<Dropdown item text='More'>
<Dropdown.Menu>
<Dropdown.Item icon='edit' text='Edit Profile' />
<Dropdown.Item icon='globe' text='Choose Language' />
<Dropdown.Item icon='settings' text='Account Settings' />
</Dropdown.Menu>
</Dropdown>
</Menu>
</div>
<div className='article'>
<div>Content</div>
</div>
</div>
</div>
)
}
}
And the style:
.toc {
width: 200px;
}
.article {
margin-left: 210px;
}
Everything is easier!
<Sidebar.Pusher style={{overflow: 'scroll', height: '100%'}}>
I think you yourself will understand why this works.
You would need to manually do it with some CSS/SCSS. Basically, you need to set the height to a fixed value.
#media only screen and (max-width: 768px) {
.ui.wide.left.sidebar, .ui.wide.right.sidebar {
height: 100vh !important;
position: absolute;
}
.pusher {
margin-left: 20px;
}
}
.pushable {
min-height: 100vh;
}
.ui.wide.left.sidebar, .ui.wide.right.sidebar {
height: 100vh;
position: fixed !important;
bottom: 0px !important;
top: 0px !important;
}
Based on the Semantic React documentation, there's an obvious way to do this, but the behavior it produces is extremely wonky: the desktop sidebar menu sticks to the top of the window as expected until you scroll near the bottom, then zooms down and attaches to the BOTTOM of the screen.
Pretty awful. This appears to be a solid fix. I've only tested it in my own setup, but it should be fairly universal, or at least a good starting point!
In order to get the sticky sidebar/mobile overlay combo described above, you would expect the relevant part of your _app.jsx to look something like this. NOT THE ONLY WAY! But also not the point, adapt to your own situation. For clarity, anything below prefixed with my... is your responsibility.
// _app.jsx
<Sidebar.Pushable>
<Sticky>
<Sidebar
as={Menu}
animation="overlay"
direction="left"
inverted
onClick={myOnHideSidebar}
onHide={myOnHideSidebar}
size="huge"
vertical
visible={mySidebarVisible}
>
<MySidebarItems />
</Sidebar>
</Sticky>
<Sidebar.Pusher dimmed={mySidebarVisible}>
<Container>
<MyPageHeader />
<Grid>
<Grid.Row>
<Grid.Column computer={4} only="computer">
<Menu fluid size="huge" vertical>
<MySidebarItems />
</Menu>
</Grid.Column>
<Grid.Column mobile={16} tablet={16} computer={12}>
<Component {...pageProps} />
</Grid.Column>
</Grid.Row>
</Grid>
<MyPageFooter />
</Container>
</Sidebar.Pusher>
</Sidebar.Pushable>
In practice, this produces the weird behavior I described above.
Here's the fix. Look for the STICKYFIX comments, and note the stylesheet entry, which is necessary because of the -webkit style.
// _app.jsx
<Sidebar.Pushable style={{ transform: 'none' }}> // STICKYFIX
<Sticky>
<Sidebar
as={Menu}
animation="overlay"
direction="left"
inverted
onClick={myOnHideSidebar}
onHide={myOnHideSidebar}
size="huge"
vertical
visible={mySidebarVisible}
>
<MySidebarItems />
</Sidebar>
</Sticky>
<Sidebar.Pusher
dimmed={mySidebarVisible}
style={{ minHeight: '100vh' }} // STICKYFIX
>
<Container>
<MyPageHeader />
<Grid>
<Grid.Row>
<Grid.Column computer={4} only="computer">
<Menu className="sidebar-menu" fluid size="huge" vertical> // STICKYFIX
<SidebarItems />
</Menu>
</Grid.Column>
<Grid.Column mobile={16} tablet={16} computer={12}>
<Component {...pageProps} />
</Grid.Column>
</Grid.Row>
</Grid>
<MyPageFooter />
</Container>
</Sidebar.Pusher>
</Sidebar.Pushable>
// styles.css
.sidebar-menu {
position: sticky;
position: -webkit-sticky;
top: 20px;
}
Here's my gist detailing the fix.

Resources