I have no clue how to display the elements selected in my treeview. Ideas or tips ? please
The purpose of this treeview is to filter the data for export purposes.
The origin of the code comes from here (https://mui.com/material-ui/react-tree-view/#customization)
import React, {useEffect, useState} from 'react';
import ReactDOMServer from 'react-dom/server';
import TreeView from '#mui/lab/TreeView';
import TreeItem from '#mui/lab/TreeItem';
function MinusSquare(props) {
return (
<SvgIcon fontSize="inherit" style={{ width: 25, height: 25 }} {...props}>
{/* tslint:disable-next-line: max-line-length */}
<path d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z" />
</SvgIcon>
);
}
function PlusSquare(props) {
return (
<SvgIcon fontSize="inherit" style={{ width: 25, height: 25 }} {...props}>
{/* tslint:disable-next-line: max-line-length */}
<path d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 12.977h-4.923v4.896q0 .401-.281.682t-.682.281v0q-.375 0-.669-.281t-.294-.682v-4.896h-4.923q-.401 0-.682-.294t-.281-.669v0q0-.401.281-.682t.682-.281h4.923v-4.896q0-.401.294-.682t.669-.281v0q.401 0 .682.281t.281.682v4.896h4.923q.401 0 .682.281t.281.682v0q0 .375-.281.669t-.682.294z" />
</SvgIcon>
);
}
function CloseSquare(props) {
return (
<SvgIcon
className="close"
fontSize="inherit"
style={{ width: 25, height: 25 }}
{...props}
>
{/* tslint:disable-next-line: max-line-length */}
<path d="M17.485 17.512q-.281.281-.682.281t-.696-.268l-4.12-4.147-4.12 4.147q-.294.268-.696.268t-.682-.281-.281-.682.294-.669l4.12-4.147-4.12-4.147q-.294-.268-.294-.669t.281-.682.682-.281.696 .268l4.12 4.147 4.12-4.147q.294-.268.696-.268t.682.281 .281.669-.294.682l-4.12 4.147 4.12 4.147q.294.268 .294.669t-.281.682zM22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0z" />
</SvgIcon>
);
}
function TransitionComponent(props) {
const style = useSpring({
from: {
opacity: 0,
transform: 'translate3d(20px,0,0)',
},
to: {
opacity: props.in ? 1 : 0,
transform: `translate3d(${props.in ? 0 : 20}px,0,0)`,
},
});
return (
<animated.div style={style}>
<Collapse {...props} />
</animated.div>
);
}
TransitionComponent.propTypes = {
/**
* Show the component; triggers the enter or exit states
*/
in: PropTypes.bool,
};
const StyledTreeItem = styled((props) => (
<TreeItem {...props} TransitionComponent={TransitionComponent} />
))(({ theme }) => ({
[`& .${treeItemClasses.iconContainer}`]: {
'& .close': {
opacity: 0.3,
},
},
[`& .${treeItemClasses.group}`]: {
marginLeft: 15,
paddingLeft: 18,
borderLeft: `1px dashed ${alpha(theme.palette.text.primary, 0.4)}`,
},
}));
const TreeViewBuilder = ({data, ChoiceDate, ChoiceModality}) => {
return (
<>
{data.map((key, index) => {
if (ChoiceModality.includes(data[index]['modality']) && (Date.parse(ChoiceDate[0]) <= Date.parse(data[index]['startDate']) && Date.parse(data[index]['startDate']) <= Date.parse(ChoiceDate[1]))) {
return <StyledTreeItem nodeId={data[index]['series'][0] + "_" + index} label={data[index]['series'][0]}>
<StyledTreeItem nodeId={data[index]['series'][0] + "_" + data[index]['modality']} label={data[index]['modality']}/>
</StyledTreeItem>
}
})
}
</>
);
};
export default TreeViewBuilder;
Here you will find an example of the display I have.
you can get selected node/nodes of treeview from the onNodeSelect event on TreeView element like this
<TreeView
aria-label="customized"
defaultExpanded={['1']}
defaultCollapseIcon={<MinusSquare />}
defaultExpandIcon={<PlusSquare />}
defaultEndIcon={<CloseSquare />}
sx={{ height: 264, flexGrow: 1, maxWidth: 400, overflowY: 'auto' }}
onNodeSelect={handleSelectedItems}
>
The callback event will recieve nodeId/nodeIds like this
const handleSelectedItems = (event, nodeId) => {
console.log(nodeId);
}
If you are using multiSelect prop, nodeId will be an array containing the nodeIds of all selected nodes. If you are not using multiSelect it will be string with the selected node's nodeId.
Related
Why my react-native-shadow-2 is not working ? i have installed npm i react-native-shadow-2 and react-native-svg but still its not working tell how to fix it i have also restarted my project i am using android i have also tried to do npx react-native link but that also did not work you can see whats the problem the shadow i have marked is wrong it should be like this
How its showing currently
my code
import React, { useRef } from 'react'
import {
View,
Text,
Image,
Animated,
FlatList,
} from 'react-native';
import {
Home,
Profile,
Search
} from '../../screens';
import { COLORS, FONTS, SIZES, constants } from '../../constants';
import { Shadow } from 'react-native-shadow-2';
const bottom_tabs = constants.bottom_tabs.map((bottom_tabs) => ({
...bottom_tabs,
ref: React.createRef()
}))
const MainLayout = () => {
const flatListRef = React.useRef()
const scrollX = React.useRef(new Animated.Value(0)).current;
function renderContent() {
return (
<View style={{
flex: 1,
}}
>
<Animated.FlatList
ref={flatListRef}
horizontal
pagingEnabled
snapToAlignment="center"
snapToInterval={SIZES.width}
decelerationRate="fast"
showsHorizontalScrollIndicator={false}
data={constants.bottom_tabs}
keyExtractor={item => `Main-${item.id}`}
onScroll={
Animated.event([
{ nativeEvent: { contentOffset: { x: scrollX } } }
], {
useNativeDriver: false
})
}
renderItem={({ item, index }) => {
return (
<View
style={{
height: SIZES.height,
width: SIZES.width
}}
>
{item.label == constants.screens.home && <Home />}
{item.label == constants.screens.search && <Search />}
{item.label == constants.screens.profile && <Profile />}
</View>
)
}}
/>
</View>
)
}
function renderBottonTab() {
return (
<View
style={{
marginBottom: 20,
paddingHorizontal: SIZES.padding,
paddingVertical: SIZES.radius
}}
>
<Shadow size={[SIZES.width - (SIZES.padding * 2), 85]}>
<View
style={{
flex: 1,
borderRadius: SIZES.radius,
backgroundColor: COLORS.primary3
}}
>
</View>
</Shadow>
</View>
)
}
return (
<View
style={{
flex: 1,
backgroundColor: COLORS.white,
}}
>
{/* Content */}
{renderContent()}
{/* Bottom Tab */}
{renderBottonTab()}
</View>
)
}
export default MainLayout;
I am trying to fetch data with RTK Query in next.js project and everything were fine until I had to fetch some more data from /api/exams endpoint. I have fetched almost everything from that endpoint and i know that's working fine but i still can't fetch some data from it. I'll provide screenshots of all code that's related to it. ok so here is the code where endpoints are:
Then let's continue with exams-response where i define body of endpoint:
Now I will provide code in my custom hook where i import that data from api/exams endpoint query:
And now i will show code of the actual page where i use them and where i think problem may lie also with another file which i will provide after this:
import { memo } from "react"
import { useIntl } from "react-intl"
import Stack from "#mui/material/Stack"
import Typography from "#mui/material/Typography"
import { ExamoTypesEnum } from "src/common/types/examo-types-enum"
import { rolesEnum } from "src/core/roles-enum"
import { useExamAssign } from "src/features/exams/hooks/use-exam-assign"
import { useExams } from "src/features/exams/hooks/use-exams"
import { useStartExam } from "src/features/exams/hooks/use-start-exam"
import { useIsMobile } from "src/helpers/use-is-mobile"
import { useAppSelector } from "src/redux/hooks"
import { ExamoCard } from "src/ui/examo/examo-card"
import { ExamoCardsGrid } from "src/ui/examo/examo-cards-grid"
import { ExamoHeader } from "src/ui/examo/examo-header"
import { CustomizedDialogs } from "src/ui/filter"
import { LoadingSpinner } from "src/ui/loading-spinner"
import { styled } from "src/ui/theme"
import { UnauthenticatedComponent } from "src/ui/unauthenticated"
import { useTags } from "../tags/hooks/use-tags"
import { useActiveExamQuery } from "./api"
const Ourbox = styled.div`
display: flex;
justify-content: space-between;
`
export const ExamsPage = memo(() => {
const isMobile = useIsMobile()
const userRole = useAppSelector((state) => state.auth.role)
const intl = useIntl()
const {
exams,
isLoadingExams,
selectedTags,
setSelectedTags,
checkedFilterTags,
setCheckedFilterTags,
} = useExams()
const { availableTags } = useTags()
const isLoadingAnExam = useAppSelector((state) => state.exam.isLoadingAnExam)
const { startAsync } = useStartExam()
const { data: activeExam, isFetching: isFetchingActiveExam } =
useActiveExamQuery(undefined, { refetchOnMountOrArgChange: 1 })
if (userRole === rolesEnum.None) {
return <UnauthenticatedComponent />
}
return (
<Stack sx={{ paddingX: isMobile ? 3 : "10vw", paddingY: 4 }} gap={4}>
<Ourbox>
<ExamoHeader
header={intl.formatMessage({
id: "exams-header",
defaultMessage: "Choose your exam",
})}
subtitle={intl.formatMessage({
id: "exams-header-subtitle",
defaultMessage:
"Our operators make quizzes and tests to help you upgrade and test your skills.",
})}
/>
<CustomizedDialogs
id="exams-page-filter"
selectedTags={selectedTags}
setSelectedTags={setSelectedTags}
availableTags={availableTags || []}
checkedFilterTags={checkedFilterTags}
setCheckedFilterTags={setCheckedFilterTags}
/>
</Ourbox>
{isLoadingExams && <LoadingSpinner />}
{!isLoadingExams && (!exams || exams.length === 0) && (
<Typography>
{intl.formatMessage({
id: "no-exams-available",
defaultMessage: "No exams available",
})}
</Typography>
)}
{exams && exams.length > 0 && (
<ExamoCardsGrid>
{exams.map((exam) => (
<ExamoCard
key={exam.id}
type={ExamoTypesEnum.EXAM}
useAssign={useExamAssign}
isStartButtonDisabled={
isLoadingAnExam ||
isFetchingActiveExam ||
(activeExam?.exam?.id !== undefined &&
exam.id !== activeExam.exam.id)
}
isResuming={
activeExam?.exam?.id !== undefined &&
exam.id === activeExam.exam.id
}
handleStart={() => startAsync(exam.id)}
isLoading={isLoadingAnExam}
title={exam.title}
duration={exam.duration}
tags={[
...new Set(exam.templates?.flatMap((et) => et.tags) || []),
]}
numberOfQuestions={exam.templates.reduce(
(total, current) => total + current.numberOfQuestions,
0,
)}
deadline-start={exam["deadline-start"]}
deadline-end={exam["deadline-end"]}
i={exam.id}
/>
))}
</ExamoCardsGrid>
)}
</Stack>
)
})
and the last one which is as mapped through in above the code. so it's :
import { memo } from "react"
import * as React from "react"
import { useIntl } from "react-intl"
import AccessTimeIcon from "#mui/icons-material/AccessTime"
import ExpandMoreIcon from "#mui/icons-material/ExpandMore"
import MoreHorizIcon from "#mui/icons-material/MoreHoriz"
import Box from "#mui/material/Box"
import Card from "#mui/material/Card"
import MuiChip from "#mui/material/Chip"
import Collapse from "#mui/material/Collapse"
import Grid from "#mui/material/Grid"
import IconButton, { IconButtonProps } from "#mui/material/IconButton"
import Stack from "#mui/material/Stack"
import { styled } from "#mui/material/styles"
import Typography from "#mui/material/Typography"
import { ExamoTypesEnum } from "src/common/types/examo-types-enum"
import { useAssignType } from "src/common/types/use-assign-type"
import { useAppSelector } from "src/redux/hooks"
import { ExamoAssignTo } from "src/ui/examo/examo-assign-to"
import { ExamoStartDialogBtn } from "src/ui/examo/examo-start-btn"
interface props {
duration: string | null
title: string
tags: string[] | null
numberOfQuestions: number | null
isLoading: boolean
handleStart: () => void
useAssign: useAssignType
isStartButtonDisabled: boolean
isResuming: boolean
type: ExamoTypesEnum
i: number
"deadline-start": string
"deadline-end": string
}
export const ExamoCard = memo(
({
duration,
tags,
title,
isLoading,
numberOfQuestions,
isStartButtonDisabled,
isResuming,
type,
handleStart,
useAssign,
i,
"deadline-end": deadlineEnd,
"deadline-start": deadlineStart,
}: props) => {
console.log(deadlineStart)
const intl = useIntl()
const user = useAppSelector((state) => state.auth)
const durationHours = duration?.split(":")[0]
const durationMinutes = duration?.split(":")[1]
// const [expanded, setExpanded] = React.useState(false)
const [expandedId, setExpandedId] = React.useState(-1)
// const handleExpandClick = () => {
// setExpanded(!expanded)
// }
const handleExpandClick = (i: number) => {
setExpandedId(expandedId === i ? -1 : i)
}
const preventParentOnClick = (event: React.MouseEvent<HTMLElement>) => {
event.stopPropagation()
}
interface ExpandMoreProps extends IconButtonProps {
expand: boolean
}
const ExpandMore = styled((props: ExpandMoreProps) => {
const { expand, ...other } = props
return <IconButton {...other} />
})(({ theme, expand }) => ({
transform: !expand ? "rotate(0deg)" : "rotate(180deg)",
transition: theme.transitions.create("transform", {
duration: theme.transitions.duration.shortest,
}),
}))
return (
<Grid item xs={12} lg={6}>
<Card
onClick={() => handleExpandClick(i)}
aria-expanded={expandedId === i}
elevation={2}
sx={{
padding: "1rem",
height: "100%",
borderRadius: "1rem",
border: "solid 1px var(--palette-grey-400)",
transition: "all 0.1s ease-in-out",
":hover": {
backgroundColor: "var(--palette-grey-100)",
cursor: "pointer",
},
}}
>
<Stack direction="row" justifyContent="space-between">
<Stack
gap={numberOfQuestions ? 1.5 : 6}
sx={{
width: "100%",
}}
>
<Stack
direction="row"
sx={{
justifyContent: "space-between",
}}
>
<Typography
variant="h5"
sx={{ whiteSpace: "pre-wrap", wordBreak: "break-all" }}
>
{title}
</Typography>
<ExpandMore expand={expandedId === i}>
<ExpandMoreIcon />
</ExpandMore>
<Stack
direction="row"
gap={1}
sx={{
justifyContent: "flex-end",
alignItems: "center",
marginTop: "-1.75rem",
}}
>
<AccessTimeIcon />
<Typography
whiteSpace="nowrap"
variant="h6"
>{`${durationHours}h ${durationMinutes}m`}</Typography>
</Stack>
</Stack>
<Collapse in={expandedId === i} timeout="auto" unmountOnExit>
<Stack direction="row" gap={4}>
{numberOfQuestions && (
<Typography variant="h6">
{`${numberOfQuestions} ${intl.formatMessage({
id: "questions",
defaultMessage: "Questions",
})}`}
</Typography>
)}
</Stack>
<Stack
direction="row"
sx={{ flexWrap: "wrap", gap: 1, marginTop: "1rem" }}
>
{tags?.map((t, index) => (
<MuiChip
key={index}
label={t}
variant="filled"
sx={{ fontWeight: "bold" }}
color="secondary"
size="small"
/>
))}
</Stack>
</Collapse>
</Stack>
<Stack
sx={{ marginTop: "-0.5rem" }}
justifyContent="space-between"
alignItems="center"
spacing={user.role === "Operator" ? 0.1 : 1}
>
<MoreHorizIcon sx={{ marginLeft: "2rem" }} />
<Box onClick={preventParentOnClick}>
<Stack sx={{ marginLeft: "1.5rem" }}>
{user.role === "Operator" && (
<ExamoAssignTo useAssign={useAssign} title={title} />
)}
</Stack>
</Box>
<Box onClick={preventParentOnClick}>
<ExamoStartDialogBtn
type={type}
isResuming={isResuming}
handleStart={handleStart}
isLoading={isLoading}
isDisabled={isStartButtonDisabled}
/>
</Box>
</Stack>
</Stack>
</Card>
</Grid>
)
},
)
To sum up guys, I want to also fetch deadlineStart and deadlineEnd but i can't. I think problem is in last file or second to last, because maybe am not defining them properly in interface props in last code. And also i almost forgot to mention that when I try to console.log(deadlineStart) in the last code it says undefined in the browser. Edited Network Pic :
here is screenshot when i console log single exams :
I experienced React before and trying to learn React Native so here is my problem about react-navigation
I have some separated class or js files for react-navigation
main.js
type Props = {};
class Main extends Component<Props> {
componentDidMount() {
SplashScreen.hide();
}
render() {
return (
<Container>
<MainPostsList />
</Container>
)
}
};
const Stack = createStackNavigator();
//its not a class for this navigation thing
function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={Main} />
<Stack.Screen name="Post" component={PostsPage} />
</Stack.Navigator>
</NavigationContainer>
)
}
export default App;
postlist.js
export default class MainPostLists extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
data: [],
}
}
componentDidMount() {
getMainPosts().then(data => {
this.setState({
isLoading: false,
data: data
})
}, error => {
Alert.alert('An error has occurred', 'Please try again later.')
})
}
render() {
console.log(this.state.data.data);
let view = this.state.isLoading ? (
<View>
<ActivityIndicator animating={this.state.isLoading} size={"large"} style={{paddingTop: 40}} />
</View>
) : (
<List
dataArray={this.state.data.data}
renderRow={(item) => {
return <MenuPostItem data={item} />
}} />
)
return (
<Container>
<Content>
{view}
</Content>
</Container>
)
}
}
postlistitem.js
export default class MenuPostItem extends Component {
constructor(props) {
super(props);
this.data = props.data;
}
render() {
console.log(this.data)
const goToPostPage = (postlink) => {
console.log("Going to Post Page " + postlink);
this.props.navigation.navigate('Post')
};
return (
<ListItem button onPress={() => {goToPostPage(this.data.postlink)}} onLongPress>
<Left>
{this.data.type === 'video' ? (
<Thumbnail style={{ width: 150, height: 84, borderRadius: 0 }} source={{
uri: `http://` + hostname + `:5000/media/streamimage?src=${this.data.media.videothumbsrc}`
//uri: ImageThumbTest
}} />
) : (null)}
{this.data.type === 'text' ? (
<Thumbnail style={{ width: 150, height: 84, borderRadius: 0 }} />,
<Text style={{paddingLeft: 40, paddingRight: 40}}>Text Post</Text>
) : (null)}
</Left>
<Body>
<Text>{this.data.title}</Text>
<Text note numberOfLines={3}>{this.data.description}</Text>
</Body>
</ListItem>
)
}
}
this error appears on Android as I click on an item from the list:
it was supposed to go to a post dynamic page
MenuPostItem doesn't have any access to navigation props.
Here how you get access to navigation props
Passing the navigation prop to MenuPostItem from MainPostLists component
<List
dataArray={this.state.data.data}
renderRow={(item) => {
return <MenuPostItem data={item} navigation={this.props.navigation} />;
}}
/>;
Using useNavigation hooks (you need to change your MenuPostItem to the functional component to use useNavigation hook)
a minimal example
import * as React from "react";
import { Button } from "react-native";
import { useNavigation } from "#react-navigation/native";
function MenuPostItem() {
const navigation = useNavigation();
return (
<Button
title="go to post"
onPress={() => {
navigation.navigate("Post");
}}
/>
);
}
learn more about useNavigation
create a function for list items like below
const MenuPostItem = ({data, onPress}) = > {
return (
<ListItem button onPress={() => onPress('Post')}>
<Left>
{data.type === 'video' ? (
<Thumbnail
style={{ width: 150, height: 84, borderRadius: 0 }}
source={{uri: `http://` + hostname + `:5000/media/streamimage?src=${data.media.videothumbsrc}`}}
/>
) : (null)}
{data.type === 'text' ? (
<Thumbnail style={{ width: 150, height: 84, borderRadius: 0 }} />,
<Text style={{paddingLeft: 40, paddingRight: 40}}>Text Post</Text>
) : (null)}
</Left>
<Body>
<Text>{data.title}</Text>
<Text note numberOfLines={3}>{data.description}</Text>
</Body>
</ListItem>
)}
export default MenuPostItem;
and then in your MainPostLists.js file -
<List
dataArray={this.state.data.data}
renderRow={(item) => {
return <MenuPostItem data={item} navigation={this.props.navigation.navigate} />;
}}
/>;
I am trying to achieve an image/video carousel using https://jossmac.github.io/react-images/
and it should be like this including modal :
I following the code snippet given there but it's not working and I don't see any step by step guide to making that carousel.
class Gall extends Component {
state = { modalIsOpen: false }
toggleModal = () => {
this.setState(state => ({ modalIsOpen: !state.modalIsOpen }));
}
render() {
const { modalIsOpen } = this.state;
return (
<ModalGateway>
{modalIsOpen ? (
<Modal onClose={this.toggleModal}>
<Carousel views={images} />
</Modal>
) : null}
</ModalGateway>
);
}
}
export default Gall;
can anyone please help with a codesandbox?
Also is it possible to trigger the modal with the current active image?
Thanks in advance.
There is a link in their docs to the source
// #flow
// #jsx glam
import glam from 'glam';
import React, { Component, Fragment } from 'react';
import { type ProviderProps } from '../../ImageProvider';
import Carousel, { Modal, ModalGateway } from '../../../src/components';
import { FooterCaption } from '../components';
import { getAltText } from '../formatters';
type State = {
selectedIndex?: number,
lightboxIsOpen: boolean,
};
export default class Home extends Component<ProviderProps, State> {
state = {
selectedIndex: 0,
lightboxIsOpen: false,
};
toggleLightbox = (selectedIndex: number) => {
this.setState(state => ({
lightboxIsOpen: !state.lightboxIsOpen,
selectedIndex,
}));
};
render() {
const { images, isLoading } = this.props;
const { selectedIndex, lightboxIsOpen } = this.state;
return (
<Fragment>
{!isLoading ? (
<Gallery>
{images.map(({ author, caption, source }, j) => (
<Image onClick={() => this.toggleLightbox(j)} key={source.thumbnail}>
<img
alt={caption}
src={source.thumbnail}
css={{
cursor: 'pointer',
position: 'absolute',
maxWidth: '100%',
}}
/>
</Image>
))}
</Gallery>
) : null}
<ModalGateway>
{lightboxIsOpen && !isLoading ? (
<Modal onClose={this.toggleLightbox}>
<Carousel
components={{ FooterCaption }}
currentIndex={selectedIndex}
formatters={{ getAltText }}
frameProps={{ autoSize: 'height' }}
views={images}
/>
</Modal>
) : null}
</ModalGateway>
</Fragment>
);
}
}
const gutter = 2;
const Gallery = (props: any) => (
<div
css={{
overflow: 'hidden',
marginLeft: -gutter,
marginRight: -gutter,
}}
{...props}
/>
);
const Image = (props: any) => (
<div
css={{
backgroundColor: '#eee',
boxSizing: 'border-box',
float: 'left',
margin: gutter,
overflow: 'hidden',
paddingBottom: '16%',
position: 'relative',
width: `calc(25% - ${gutter * 2}px)`,
':hover': {
opacity: 0.9,
},
}}
{...props}
/>
);
As per Material Design guidelines:
Upon scrolling, the top app bar can […] transform in the following ways:
- Scrolling upward hides the top app bar
- Scrolling downward reveals the top app bar
When the top app bar scrolls, its elevation above other elements becomes apparent.
Is there any built-in approach to do this in material-ui-next or should it be considered as a new feature? Can you give a hint on how to achieve the animation of the AppBar component as described in the guidelines?
To my knowledge, there's no out-of-the-box solution for this at the moment. It's quite easy to implement though. Here is a snippet that subscribes to scroll events and hides or shows the AppBar accordingly:
const styles = {
root: {
flexGrow: 1,
},
show: {
transform: 'translateY(0)',
transition: 'transform .5s',
},
hide: {
transform: 'translateY(-110%)',
transition: 'transform .5s',
},
};
class CollapsibleAppBar extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
shouldShow: null,
};
this.lastScroll = null;
this.handleScroll = this.handleScroll.bind(this);
// Alternatively, you can throttle scroll events to avoid
// updating the state too often. Here using lodash.
// this.handleScroll = _.throttle(this.handleScroll.bind(this), 100);
}
componentDidMount() {
window.addEventListener('scroll', this.handleScroll, { passive: true });
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll);
}
handleScroll(evt) {
const lastScroll = window.scrollY;
if (lastScroll === this.lastScroll) {
return;
}
const shouldShow = (this.lastScroll !== null) ? (lastScroll < this.lastScroll) : null;
if (shouldShow !== this.state.shouldShow) {
this.setState((prevState, props) => ({
...prevState,
shouldShow,
}));
}
this.lastScroll = lastScroll;
}
render() {
const { classes } = this.props;
return (
<AppBar
position="fixed"
color="default"
className={
`${classes.root} ${
this.state.shouldShow === null ? '' : (
this.state.shouldShow ? classes.show : classes.hide
)
}`
}
>
<Toolbar>
<Typography variant="title" color="inherit">
Title
</Typography>
</Toolbar>
</AppBar>
);
}
}
CollapsibleAppBar.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(CollapsibleAppBar);
For those who are still looking for built-in feature, Hide appbar on scroll is available in material-ui.
in the current version of Material-ui, you can simply use the following
import clsx from "clsx";
import useScrollTrigger from "#material-ui/core/useScrollTrigger";
const trigger = useScrollTrigger();
<AppBar className={trigger ? classes.show : classes.hide}>
</AppBar>
https://material-ui.com/components/app-bar/#usescrolltrigger-options-trigger
this seem to work for me
import {
useScrollTrigger,
Fab,
Zoom,
} from '#mui/material';
...
function ElevationScroll(props) {
const { children } = props;
const theme = useTheme();
const trigger = useScrollTrigger({
disableHysteresis: true,
threshold: 0,
});
return React.cloneElement(children, {
sx: trigger
? {
bgcolor: theme.palette.primary.dark,
'transition-duration': '500ms',
'transition-property':
'padding-top, padding-bottom, background-color',
'transition-timing-function': 'ease-in-out',
}
: {
pt: 2,
pb: 2,
bgcolor: theme.palette.primary.main,
},
elevation: trigger ? 5 : 0,
});
}
function ScrollTop(props) {
const { children } = props;
const trigger = useScrollTrigger({
disableHysteresis: true,
threshold: 200,
});
const handleClick = (event) => {
const anchor = (event.target.ownerDocument || document).querySelector(
'#back-to-top-anchor'
);
if (anchor) {
anchor.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
};
return (
<Zoom in={trigger}>
<Box
onClick={handleClick}
role="presentation"
sx={{ position: 'fixed', bottom: 16, right: 16, zIndex: 1 }}
>
{children}
</Box>
</Zoom>
);
}
...
return (
<React.Fragment>
<ElevationScroll {...props}>
<AppBar position="sticky">
...
</AppBar>
</ElevationScroll>
<Toolbar
id="back-to-top-anchor"
className="_Toolbar"
sx={{
minHeight: '0 !important',
}}
/>
<ScrollTop {...props}>
<Fab color="secondary" size="small" aria-label="scroll back to top">
<KeyboardArrowUpIcon />
</Fab>
</ScrollTop>
</React.Fragment>
this seem to work for me
import {
useScrollTrigger,
Fab,
Zoom,
} from '#mui/material';
...
function ElevationScroll(props) {
const { children } = props;
const theme = useTheme();
const trigger = useScrollTrigger({
disableHysteresis: true,
threshold: 0,
});
return React.cloneElement(children, {
sx: trigger
? {
bgcolor: theme.palette.primary.dark,
'transition-duration': '500ms',
'transition-property':
'padding-top, padding-bottom, background-color',
'transition-timing-function': 'ease-in-out',
}
: {
pt: 2,
pb: 2,
bgcolor: theme.palette.primary.main,
},
elevation: trigger ? 5 : 0,
});
}
function ScrollTop(props) {
const { children } = props;
const trigger = useScrollTrigger({
disableHysteresis: true,
threshold: 200,
});
const handleClick = (event) => {
const anchor = (event.target.ownerDocument || document).querySelector(
'#back-to-top-anchor'
);
if (anchor) {
anchor.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
};
return (
<Zoom in={trigger}>
<Box
onClick={handleClick}
role="presentation"
sx={{ position: 'fixed', bottom: 16, right: 16, zIndex: 1 }}
>
{children}
</Box>
</Zoom>
);
}
...
return (
<React.Fragment>
<ElevationScroll {...props}>
<AppBar position="sticky">
...
</AppBar>
</ElevationScroll>
<Toolbar
id="back-to-top-anchor"
className="_Toolbar"
sx={{
minHeight: '0 !important',
}}
/>
<ScrollTop {...props}>
<Fab color="secondary" size="small" aria-label="scroll back to top">
<KeyboardArrowUpIcon />
</Fab>
</ScrollTop>
</React.Fragment>
https://mui.com/components/app-bar/#usescrolltrigger-options-trigger