@material-ui/lab#Skeleton JavaScript Examples
The following examples show how to use
@material-ui/lab#Skeleton.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: ContactInfo.js From app with MIT License | 6 votes |
// Wrap the main component so the loading widget is limited to just the section where the contact
// info is displayed.
function ContactInfoWrapped(props) {
return (
<Suspense fallback={<Skeleton />}>
<ContactInfo {...props} />
</Suspense>
);
}
Example #2
Source File: blogs.js From dscbppimt-official-website with MIT License | 6 votes |
function Blogs() {
const [Blogs, setBlogs] = useState([]);
const URL = "https://dscbppimt-cms.herokuapp.com"
useEffect(() => {
const data = async() => {
const res = await Axios.get("https://dscbppimt-cms.herokuapp.com/our-blogs?_sort=Date:desc");
setBlogs(res.data);
}
data();
},[])
return (
<Layout>
<Container style={{marginBottom : '4em'}}>
<Typography variant="h4" style={{fontWeight : '500', margin : '1em 0px'}}>Our Blogs</Typography>
<Grid container spacing={2}>
{Blogs.length === 0 ? <Skeleton variant="rect" width="100%" height="150px"/> : Blogs.map(event => (
<Grid item xs={12} sm={6} md={12} key={event._id}>
<BlogCard
Image={URL+event.Image.formats.thumbnail.url}
title={event.Title}
speaker={event.Author}
discription={event.Description}
Platform={event.Platform}
url={event.Read}
data={event}
/>
</Grid>
))}
</Grid>
</Container>
</Layout>
)
}
Example #3
Source File: RecipeList.jsx From archeage-tools with The Unlicense | 6 votes |
render() {
const { maxHeight } = this.props;
const { recipeList, loaded } = this.state;
if (loaded !== this.getRecipeKey()) {
const skeletons = [];
for (let i = 0; i < 10; i++) {
skeletons.push(<Skeleton variant="text" key={`skele-${i}`} />);
}
return (
<div className="recipe-list">
{skeletons}
</div>
);
}
return (
<Typography component="div" className="recipe-list" style={{ maxHeight: maxHeight > 0 ? maxHeight : null }}>
{recipeList.map(cat => this.renderRecipeCategory(cat))}
</Typography>
);
}
Example #4
Source File: SkillLink.jsx From archeage-tools with The Unlicense | 6 votes |
render() {
const { id, skill } = this.props;
const { name, icon, passive, ancestralElement: element } = skill;
let text = '';
if (passive) {
text += '[Passive] ';
} else if (element && element !== ELEMENT.BASIC) {
text += `[${element}] `;
}
text += name;
if (!icon) {
text = (
<Skeleton
variant="text"
style={{ display: 'inline-block', marginLeft: 4, width: 80, height: 20, transform: 'none' }}
/>
);
}
return (
<SkillTooltip skillId={id}>
<Link className="inline-link">
<SkillIcon id={id} className="inline" disableTooltip />
{text}
</Link>
</SkillTooltip>
);
}
Example #5
Source File: Skill.jsx From archeage-tools with The Unlicense | 6 votes |
render() {
const { spentPoints, onClick, learned, remainingPoints, noRequirement, className, disableTooltip, ancestral } = this.props;
const { id, icon, requiredLevel, passive } = this.props;
const pointsRequired = passive ? requiredLevel : getPointReq(requiredLevel);
const disabled = passive ? !learned
: !learned && !ancestral && (spentPoints < pointsRequired || remainingPoints === 0);
if (!icon) {
return (
<span className={cn('skill', className)}>
<Skeleton
variant="rect"
width="100%"
height="100%"
/>
</span>
);
}
return (
<SkillTooltip
skillId={id}
disabled={disabled && spentPoints < pointsRequired}
spentPoints={spentPoints}
disableTooltip={disableTooltip}
>
<span
className={cn('skill', className, { disabled, 'available': !disabled && !learned, ancestral })}
onClick={disabled ? null : onClick}
data-points-req={ancestral || learned || noRequirement || spentPoints >= pointsRequired ? 0 : pointsRequired}
>
<img src={`/images/icon/${icon}.png`} alt="" />
</span>
</SkillTooltip>
);
}
Example #6
Source File: NpcLink.jsx From archeage-tools with The Unlicense | 6 votes |
render() {
const { id, name, style, noLink } = this.props;
if (!name) {
return <Skeleton variant="text" style={{ display: 'inline-block', marginLeft: 4, width: 80 }} />;
}
if (noLink) {
return (
<Typography component="span" style={style}>
{name}
</Typography>
);
}
return (
<NpcTooltip npcId={id} disabled={!name}>
<Link className="inline-link" style={style}>
{name}
</Link>
</NpcTooltip>
);
}
Example #7
Source File: DoodadLink.jsx From archeage-tools with The Unlicense | 6 votes |
render() {
const { id, name, style, noLink } = this.props;
if (!name) {
return <Skeleton variant="text" style={{ display: 'inline-block', marginLeft: 4, width: 80 }} />;
}
if (noLink) {
return (
<Typography component="span" style={style}>
{name}
</Typography>
);
}
return (
<DoodadTooltip doodadId={id} disabled={!name}>
<Link className="inline-link" style={style}>
{name}
</Link>
</DoodadTooltip>
);
}
Example #8
Source File: Item.jsx From archeage-tools with The Unlicense | 6 votes |
render() {
const { id, name, icon, count, defaultGrade, overlay, inline, tooltipDisabled, showCount } = this.props;
let { grade } = this.props;
if (defaultGrade !== grade && defaultGrade > 1) {
grade = defaultGrade;
}
if (!icon) return (
<Skeleton
variant="rect"
width={inline ? 20 : '100%'}
height={inline ? 20 : '100%'}
style={{ display: inline ? 'inline-block' : 'block' }}
/>
);
return (
<ItemTooltip itemId={id} grade={grade} disabled={tooltipDisabled || !name}>
<span className={cn('item-icon', { [overlay]: Boolean(overlay), inline, showCount })} data-grade={grade}
data-id={id}>
<img src={`/images/icon/${icon}.png`} alt={name} />
{((count > 0 && !inline) || showCount) && <span className="count">{count}</span>}
</span>
</ItemTooltip>
);
}
Example #9
Source File: PublicComments.js From app with MIT License | 6 votes |
function PublicComments({ requestId }) {
const user = useUser();
return (
<>
<Typography variant="h6">Public Comments</Typography>
<Suspense
fallback={
<>
<Skeleton />
<Skeleton />
<Skeleton />
</>
}>
<CommentList requestId={requestId} />
</Suspense>
{user && <CommentEntry requestId={requestId} />}
</>
);
}
Example #10
Source File: ItemLink.jsx From archeage-tools with The Unlicense | 5 votes |
render() {
const { id, item, plural, count, style, noLink, name } = this.props;
let text = '';
let width = 80;
if (count > 1) {
text += `${count} `;
}
if (name !== null) {
text += name;
if (name === '') {
width = 20;
}
} else {
text += item.name;
if (count !== 1 || plural !== null) {
text += (plural !== null ? plural : 's');
}
}
if (!item.icon) {
text = <Skeleton variant="text" style={{ display: 'inline-block', marginLeft: 4, width }} />;
}
if (noLink) {
return (
<Typography component="span" style={style}>
<Item id={id} inline />
{text}
</Typography>
);
}
return (
<ItemTooltip itemId={id} disabled={!item.icon}>
<Link className="inline-link" style={style}>
<Item id={id} inline tooltipDisabled />
{text}
</Link>
</ItemTooltip>
);
}
Example #11
Source File: QuestLink.jsx From archeage-tools with The Unlicense | 5 votes |
render() {
const { id, name, flags: flagsD, style, noLink, region, categoryId, category } = this.props;
if (!name) {
return <Skeleton variant="text" style={{ display: 'inline-block', marginLeft: 4, width: 80 }} />;
}
const flags = flagsD.filter(f => f.region === region);
let icon = 'quest';
if (flags.find(f => f.code === FLAG_DAILY) || flags.find(f => f.code === FLAG_WEEKLY)) {
icon = 'daily';
} else if (flags.find(f => f.code === FLAG_REPEATABLE)) {
icon = 'repeat';
} else if (categoryId === 70) {
icon = 'vocation';
} else if (category.storyCategory) {
icon = 'story';
}
if (noLink) {
return (
<Typography component="span" style={style}>
<span className={cn('quest-icon', icon)} /> {name}
</Typography>
);
}
return (
<QuestTooltip questId={id} disabled={!name}>
<span style={{ whiteSpace: 'nowrap' }}>
<span className={cn('quest-icon', icon)} />
<Link className="inline-link" style={style}>
{name}
</Link>
</span>
</QuestTooltip>
);
}
Example #12
Source File: FolioHeader.jsx From archeage-tools with The Unlicense | 5 votes |
render() {
const { open, loading, searchType, options } = this.state;
const { items, mobile } = this.props;
return (
<AppBar position="static" className="section folio-header">
<Toolbar>
{!mobile &&
<Typography variant="h5" className="title-text">Folio</Typography>}
<Autocomplete
open={open}
onOpen={() => this.setOpen(true)}
onClose={() => this.setOpen(false)}
onChange={this.handleSearch}
loading={loading}
options={options}
getOptionLabel={option => option.name || option}
filterOptions={(options) => options}
classes={{
noOptions: 'folio-no-option',
}}
renderOption={option => (
<div className="item-result" key={option.id}>
<Item id={option.id} inline />
{items[option.id]
? <Typography variant="body2">{items[option.id].name}</Typography>
: <Skeleton variant="text" />}
</div>
)}
freeSolo
onInputChange={(e, value) => {
this._handleQuery(value);
}}
renderInput={params => (
<TextField
{...params}
label={`Search by ${searchTypes.find(type => type.value === searchType).label}`}
fullWidth
variant="standard"
size="small"
margin="none"
InputProps={{
...params.InputProps,
endAdornment: (
<>
{loading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</>
),
}}
InputLabelProps={{
...params.InputLabelProps,
}}
/>
)}
/>
<RadioGroup name="search-type" value={searchType} onChange={this.handleTypeChange} row={!mobile}>
{searchTypes.map(searchType => (
<FormControlLabel
control={<Radio size="small" color="primary" />}
{...searchType}
key={searchType.value}
/>
))}
</RadioGroup>
</Toolbar>
</AppBar>
);
}
Example #13
Source File: Discussion.js From app with MIT License | 5 votes |
/*
* This component assumes that the user is logged in.
*/
function Discussion({ requestId }) {
const classes = useStyles();
const firestore = useFirestore();
const user = useUser();
const userProfileSnap = useFirestoreDoc(
firestore.doc(`${USERS_COLLECTION}/${user.uid}`),
);
const requestPublicSnap = useFirestoreDoc(
firestore.doc(`${REQUESTS_PUBLIC_COLLECTION}/${requestId}`),
);
// console.log(requestPublicSnap.metadata);
// console.log(requestPublicSnap.get('d.owner'));
if (
requestPublicSnap.metadata.hasPendingWrites ||
(requestPublicSnap.get('d.owner') !== user.uid &&
userProfileSnap.get('role') !== 'system-admin')
) {
return null;
}
return (
<Paper className={classes.paper} data-test="discussion">
<Typography variant="h6">Private Comments</Typography>
<Typography variant="body2">
These comments are only shown to the assigned volunteer and the
administrators.
</Typography>
<Suspense
fallback={
<>
<Skeleton />
<Skeleton />
<Skeleton />
</>
}>
<CommentList requestId={requestId} />
</Suspense>
{user && <CommentEntry requestId={requestId} />}
</Paper>
);
}
Example #14
Source File: MusicCard.jsx From soundly with MIT License | 5 votes |
function MusicCard(props) {
const { name, img, author_name } = props.music;
const [isHovered, setHovered] = useState(false);
function handleResponse() {
setHovered(!isHovered);
}
const dispatch = useDispatch();
function handlePlay() {
dispatch(setCurrentPlaying(props.music));
dispatch(increaseTimesPlayed(props.music.id));
}
const [loaded, setLoaded] = useState(false);
useEffect(() => {
setLoaded(true);
}, []);
return (
<div className={"music-card"}>
{!loaded ? (
<div className={"Skeleton-top"}>
<Skeleton variant="rect" width={210} height={210} />
<Box pt={0.5}>
<Skeleton />
<Skeleton width="60%" />
</Box>
</div>
) : (
<>
<div onClick={handlePlay} className={"music-card-cover"} onMouseOver={handleResponse}>
<img src={require("../assets/img/" + img).default} alt={name} style={{ borderRadius: "5px" }} />
<div className="play-circle">
<PlayCircleFilledWhiteIcon />
</div>
</div>
<React.Fragment>
<Name name={name} className={"song-name"} length={name.length} />
<Name name={author_name} className={"author-name"} length={author_name.length} />
</React.Fragment>
</>
)}
</div>
);
}
Example #15
Source File: events.js From dscbppimt-official-website with MIT License | 5 votes |
function Events() {
const classes = useStyles()
const [ Events, setEvents] = useState([]);
const [ upcomingEvents, setUpcomingEvents ] = useState([])
const [ isLoading, setLoading ] = useState(false)
const URL = "https://dscbppimt-cms.herokuapp.com/files/"
useEffect(() => {
const today = new Date()
const todayDate = today.toISOString()
console.log(todayDate)
// 2020-10-11T09:10:30.698Z
setLoading(true);
Axios.get(`https://dscbppimt-cms.herokuapp.com/our-events?Date_gte=${todayDate}&_sort=Date:desc&_limit=2`).then(res => {
console.log(res.data);
setUpcomingEvents(res.data);
setLoading(false)
});
Axios.get(`https://dscbppimt-cms.herokuapp.com/our-events?Date_lt=${todayDate}&_sort=Date:desc`).then(res => {
console.log(res.data);
setEvents(res.data);
setLoading(false)
});
},[])
return (
<Layout>
<Box>
<Container style={{marginBottom : '4em'}}>
<Typography variant="h4" style={{fontWeight : '500', margin : '1em 0px'}}>Our Events</Typography>
<Grid container spacing={2}>
{isLoading ? <Skeleton variant="rect" width="100%" height="150px"/> : upcomingEvents.length !== 0 ? upcomingEvents.map(event => (
<Grid item xs={12} sm={6} md={12} key={event._id}>
<EventCard
Image={URL+(event.Image.formats.thumbnail.url)}
title={event.Title}
speaker={event.Speaker === 'None' ? null : event.Speaker }
description={event.Description}
date={event.Date}
data={event.Image}
register={event.Register}
learn={event.Learn}
/>
</Grid>
)) : <Container style={{width: '100%', textAlign: 'center', margin: '4em 0'}}><Typography align="center" >No Upcoming Events</Typography></Container>}
</Grid>
</Container>
</Box>
<Container style={{padding : '2em'}}>
<Box style={{display : 'flex', justifyContent : 'space-between'}}>
<Typography variant="h6">Past Events</Typography>
</Box>
<TableContainer component={Paper} className={classes.tableContainer}>
<Table className={classes.table} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell align="center">Event</TableCell>
<TableCell align="center">Speaker</TableCell>
<TableCell align="center">Date</TableCell>
</TableRow>
</TableHead>
<TableBody>
{Events.map((row) => (
<TableRow key={row.Title}>
<TableCell component="th" scope="row" align="center">{row.Title}</TableCell>
<TableCell align="center">{row.Speaker}</TableCell>
<TableCell align="center">{row.Date}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Container>
</Layout>
)
}
Example #16
Source File: index.js From dscbppimt-official-website with MIT License | 5 votes |
export default function Index() {
const [Events, setEvents] = useState([]);
const [ isLoading, setLoading ] = useState(false)
const URL = "https://dscbppimt-cms.herokuapp.com"
useEffect(() => {
const data = async() => {
let dataArray = [];
const today = new Date()
const todayDate = today.toISOString()
console.log(todayDate)
// 2020-10-11T09:10:30.698Z
Axios.get(`https://dscbppimt-cms.herokuapp.com/our-events?Date_gte=${todayDate}&_sort=Date:desc&_limit=2`).then(res => {
dataArray = dataArray.concat(res.data)
console.log(dataArray);
setEvents(dataArray);
});
}
data();
},[])
return (
<Layout>
<Head>
<title>DSC BPPIMT</title>
</Head>
<Header />
<AboutCardView />
<Container>
<Box style={{padding : '2em 0px',display : 'flex', justifyContent : 'space-between'}} className={styles.eventsCard}>
<Typography variant="h5" style={{fontWeight : '600', marginBottom : '.5em'}} className={styles.title}>Upcoming <Box color="primary.main" style={{display : 'inline'}}>Events</Box> and <Box color="primary.main" style={{display : 'inline'}}>Meetups</Box>.</Typography>
<Link href="/events"><Button component="button">View All</Button></Link>
</Box>
<Grid container spacing={2} style={{padding : '0 0 2em 0'}}>
{isLoading ? <Skeleton variant="rect" width="100%" height="150px"/> : Events.length !== 0 ? Events.map(event => (
<Grid item xs={12} sm={6} md={12} key={event._id}>
<EventCard
Image={event.Image ? URL+(event.Image.url) : ''}
title={event.Title}
speaker={event.Speaker === 'None' ? null : event.Speaker }
description={event.Description}
date={event.Date}
register={event.Register}
learn={event.Learn}
/>
</Grid>
)) : <Container style={{width: '100%', textAlign: 'center', margin: '5em 0'}}><Typography align="center" >No Upcoming Events</Typography></Container>}
</Grid>
</Container>
<ContactCardView />
</Layout>
);
}
Example #17
Source File: index.jsx From redive_linebot with MIT License | 5 votes |
DataList = () => {
const [{ data = {}, error, loading }, refetch] = useAxios("/api/Game/World/Boss/Feature/Message");
const { data: messageData = [] } = data;
const columns = [
{
headerName: "頭像",
field: "icon_url",
flex: 0.5,
renderCell: genAvatar,
},
{ headerName: "訊息樣板", field: "template", flex: 2 },
{
headerName: "操作",
field: "id",
flex: 1.4,
// eslint-disable-next-line react/display-name
renderCell: rawData => <ControlButtons value={rawData.value} onDeleteComplete={refetch} />,
},
];
useEffect(() => {
refetch();
return () => {};
}, [window.location.pathname]);
if (loading) {
return <Skeleton animation="wave" variant="rect" width="100%" height={300} />;
}
if (error) {
return <Alert severity="error">發生錯誤,請確認是否有管理權限!</Alert>;
}
return (
<div style={{ width: "100%" }}>
<DataGrid
columns={columns}
rows={messageData}
autoHeight
disableColumnMenu
disableColumnFilter
disableColumnSelector
/>
</div>
);
}
Example #18
Source File: Home.jsx From soundly with MIT License | 5 votes |
function Home() {
const [screenSize, setScreenSize] = useState(undefined);
const [currMusic, setCurrMusic] = useState(null);
const [Page, setCurrPage] = useState(<MusicCardContainer />);
let pathname = window.location.pathname;
useEffect(() => {
setCurrPage(getCurrPage(pathname));
}, [pathname]);
window.addEventListener("resize", handleResize);
function handleResize() {
setScreenSize(window.innerWidth);
}
useEffect(() => {
handleResize();
return () => window.removeEventListener("resize", handleResize);
});
const useStyle = useContext(ThemeContext);
const { playing, bannerOpen } = useSelector(state => state.musicReducer);
useEffect(() => {
setCurrMusic(playing);
}, [playing]);
const [loaded, setLoaded] = useState(false);
useEffect(() => {
setLoaded(true);
}, []);
return (
<div style={useStyle.component} className={"home-container"}>
{!loaded ? (
<div className="Home-skeleton">
<Skeleton animation={"wave"} variant={"rect"} height={"100vh"} />
</div>
) : (
<>
<section className={"home-music-container"}>
<div className="sidebar-home">
<SideBar />
</div>
<div className="main-home">
<div id={"main-content"}>
{screenSize <= 970 ? <MobileTopNavigation /> : <Navigation />}
<div className={"page-content"}>
{Page}
</div>
</div>
</div>
</section>
{bannerOpen && (
<section className="current-large-banner">
<CurrentPlayingLarge />
</section>
)}
<React.Fragment>
{currMusic ? <FooterMusicPlayer music={currMusic} /> : <FooterSelectMusic />}
{/* <FooterMusicPlayer music={currMusic} /> */}
{screenSize <= 970 && <BottomNavigationMobile />}
</React.Fragment>
</>
)}
</div>
);
}
Example #19
Source File: [...categorySlug].js From react-storefront-starter-app with Apache License 2.0 | 4 votes |
Subcategory = lazyProps => {
const [store, updateStore] = useSearchResultsStore(lazyProps)
const classes = useStyles()
const theme = useTheme()
let { pageData, loading } = store
if (pageData.isLanding) {
return (
<>
<Breadcrumbs items={!loading && pageData.breadcrumbs} />
<Grid item xs={12}>
{!loading ? (
<Typography
component="h1"
variant="h4"
gutterBottom
align="center"
className={classes.landingTitleSpacing}
>
{pageData.name}
</Typography>
) : (
<Skeleton height={32} style={{ marginBottom: theme.spacing(1) }} />
)}
</Grid>
{!loading && <LandingCmsSlots cmsBlocks={pageData.cmsBlocks} />}
</>
)
}
// Here is an example of how you can customize the URL scheme for filtering and sorting - /s/1?color=red,blue=sort=pop
// Note that if you change this, you also need to change pages/api/[...categorySlug].js to correctly handle the query parameters
// you send it.
const queryForState = useCallback(state => {
const { filters, page, sort } = state
const query = {}
for (let filter of filters) {
const [name, value] = filter.split(':')
console.log(name, value)
if (query[name]) {
query[name] = `${query[name]},${value}`
} else {
query[name] = value
}
}
if (query.more) {
delete query.more
}
if (page > 0) {
query.page = page
} else {
delete query.page
}
if (sort) {
query.sort = sort
} else {
delete query.sort
}
console.log('query', query)
return query
}, [])
return (
<>
<Breadcrumbs items={!loading && pageData.breadcrumbs} />
<SearchResultsProvider store={store} updateStore={updateStore} queryForState={queryForState}>
<Container maxWidth="lg" style={{ paddingTop: theme.spacing(2) }}>
<Head>{loading ? null : <title>{pageData.title}</title>}</Head>
<BackToTop />
<Hbox align="flex-start">
<Hidden implementation="css" xsDown>
<div className={classes.sideBar}>
<Hidden xsDown>
{/* Display the filters for desktop screen sizes */}
<Filter classes={{ root: classes.sideBar }} expandAll submitOnChange />
</Hidden>
</div>
</Hidden>
<Grid container style={{ position: 'relative' }}>
<LoadMask show={store.reloading} transparent align="top" />
<Grid item xs={12}>
{!loading ? (
<Typography component="h1" variant="h6" gutterBottom>
{pageData.name}
</Typography>
) : (
<Skeleton height={32} style={{ marginBottom: theme.spacing(1) }} />
)}
</Grid>
<Grid item xs={6} style={{ paddingRight: theme.spacing(1) }}>
<Hidden implementation="css" smUp>
{/* Display a button that opens the filter drawer on mobile screen sizes */}
<FilterButton style={{ width: '100%' }} />
</Hidden>
</Grid>
<Grid item xs={6} style={{ display: 'flex', justifyContent: 'flex-end' }}>
{/* The sort button is automatically responsive. It will show as a dropdown on desktop, and open a drawer on mobile */}
<SortButton className={classes.sortButton} />
</Grid>
<Grid item xs={6}></Grid>
<Grid item xs={12} style={{ display: 'flex', justifyContent: 'flex-end' }}>
{loading ? (
<Skeleton
width={90}
height={14}
style={{ marginBottom: 4 }}
className={classes.total}
/>
) : (
<Typography variant="caption" className={classes.total}>
<span>
{pageData.total} total {pageData.total === 1 ? 'item' : 'items'}
</span>
</Typography>
)}
</Grid>
<Grid item xs={12}>
{!loading ? (
<ResponsiveTiles autoScrollToNewTiles>
{pageData.products.map((product, i) => (
<ProductItem key={product.id} product={product} index={i} />
))}
</ResponsiveTiles>
) : (
<ResponsiveTiles>
{(() => {
const tiles = []
for (let i = 0; i < 10; i++) {
tiles.push(
<div
key={i}
style={{ marginTop: theme.spacing(2), marginBottom: theme.spacing(2) }}
>
<Fill height="100%" style={{ marginBottom: theme.spacing(1) }}>
<Skeleton variant="rect" />
</Fill>
<Skeleton height={26} />
<ProductOptionSelector
skeleton={4}
variant="swatch"
size="small"
optionProps={{
size: 'small',
showLabel: false,
}}
/>
<Skeleton height={18} />
<Skeleton height={24} style={{ marginTop: '5px' }} />
</div>
)
}
return tiles
})()}
</ResponsiveTiles>
)}
</Grid>
<Grid item xs={12}>
{!loading && <ShowMore variant="button" style={{ paddingBottom: 200 }} />}
</Grid>
</Grid>
</Hbox>
</Container>
</SearchResultsProvider>
</>
)
}
Example #20
Source File: [productId].js From react-storefront-starter-app with Apache License 2.0 | 4 votes |
Product = React.memo(lazyProps => {
const theme = useTheme()
const [confirmationOpen, setConfirmationOpen] = useState(false)
const [addToCartInProgress, setAddToCartInProgress] = useState(false)
const [state, updateState] = useLazyState(lazyProps, {
pageData: { quantity: 1, carousel: { index: 0 } },
})
const classes = useStyles()
const product = get(state, 'pageData.product') || {}
const color = get(state, 'pageData.color') || {}
const size = get(state, 'pageData.size') || {}
const quantity = get(state, 'pageData.quantity')
const { actions } = useContext(SessionContext)
const { loading } = state
// This is provided when <ForwardThumbnail> is wrapped around product links
const { thumbnail } = useContext(PWAContext)
// Adds an item to the cart
const handleSubmit = async event => {
event.preventDefault() // prevent the page location from changing
setAddToCartInProgress(true) // disable the add to cart button until the request is finished
try {
// send the data to the server
await actions.addToCart({
product,
quantity,
color: color.id,
size: size.id,
})
// open the confirmation dialog
setConfirmationOpen(true)
} finally {
// re-enable the add to cart button
setAddToCartInProgress(false)
}
}
const header = (
<Row>
<Typography variant="h6" component="h1" gutterBottom>
{product ? product.name : <Skeleton style={{ height: '1em' }} />}
</Typography>
<Hbox>
<Typography style={{ marginRight: theme.spacing(2) }}>{product.priceText}</Typography>
<Rating value={product.rating} reviewCount={10} />
</Hbox>
</Row>
)
// Fetch variant data upon changing color or size options
useDidMountEffect(() => {
const query = qs.stringify({ color: color.id, size: size.id }, { addQueryPrefix: true })
fetchVariant(`/api/p/${product.id}${query}`)
.then(res => res.json())
.then(data => {
return updateState({ ...state, pageData: { ...state.pageData, ...data.pageData } })
})
.catch(e => {
if (!StaleResponseError.is(e)) {
throw e
}
})
}, [color.id, size.id])
return (
<>
<Breadcrumbs items={!loading && state.pageData.breadcrumbs} />
<Container maxWidth="lg" style={{ paddingTop: theme.spacing(2) }}>
<form onSubmit={handleSubmit} method="post" action-xhr="/api/cart">
<Grid container spacing={4}>
<Grid item xs={12} sm={6} md={5}>
<Hidden implementation="css" smUp>
{header}
</Hidden>
<MediaCarousel
className={classes.carousel}
lightboxClassName={classes.lightboxCarousel}
thumbnail={thumbnail.current}
height="100%"
media={color.media || (product && product.media)}
/>
</Grid>
<Grid item xs={12} sm={6} md={7}>
<Grid container spacing={4}>
<Grid item xs={12}>
<Hidden implementation="css" xsDown>
<div style={{ paddingBottom: theme.spacing(1) }}>{header}</div>
</Hidden>
{product ? (
<>
<Hbox style={{ marginBottom: 10 }}>
<Label>COLOR: </Label>
<Typography>{color.text}</Typography>
</Hbox>
<ProductOptionSelector
options={product.colors}
value={color}
onChange={value =>
updateState({ ...state, pageData: { ...state.pageData, color: value } })
}
strikeThroughDisabled
optionProps={{
showLabel: false,
}}
/>
</>
) : (
<div>
<Skeleton style={{ height: 14, marginBottom: theme.spacing(2) }}></Skeleton>
<Hbox>
<Skeleton style={{ height: 48, width: 48, marginRight: 10 }}></Skeleton>
<Skeleton style={{ height: 48, width: 48, marginRight: 10 }}></Skeleton>
<Skeleton style={{ height: 48, width: 48, marginRight: 10 }}></Skeleton>
</Hbox>
</div>
)}
</Grid>
<Grid item xs={12}>
{product ? (
<>
<Hbox style={{ marginBottom: 10 }}>
<Label>SIZE: </Label>
<Typography>{size.text}</Typography>
</Hbox>
<ProductOptionSelector
options={product.sizes}
value={size}
strikeThroughDisabled
onChange={value =>
updateState({ ...state, pageData: { ...state.pageData, size: value } })
}
/>
</>
) : (
<div>
<Skeleton style={{ height: 14, marginBottom: theme.spacing(2) }}></Skeleton>
<Hbox>
<Skeleton style={{ height: 48, width: 48, marginRight: 10 }}></Skeleton>
<Skeleton style={{ height: 48, width: 48, marginRight: 10 }}></Skeleton>
<Skeleton style={{ height: 48, width: 48, marginRight: 10 }}></Skeleton>
</Hbox>
</div>
)}
</Grid>
<Grid item xs={12}>
<Hbox>
<Label>QTY:</Label>
<QuantitySelector
value={quantity}
onChange={value =>
updateState({ ...state, pageData: { ...state.pageData, quantity: value } })
}
/>
</Hbox>
</Grid>
<Grid item xs={12}>
<Button
key="button"
type="submit"
variant="contained"
color="primary"
size="large"
data-th="add-to-cart"
className={clsx(classes.docked, classes.noShadow)}
disabled={addToCartInProgress}
>
Add to Cart
</Button>
<AddToCartConfirmation
open={confirmationOpen}
setOpen={setConfirmationOpen}
product={product}
color={color}
size={size}
quantity={quantity}
price={product.priceText}
/>
</Grid>
</Grid>
</Grid>
</Grid>
<Grid item xs={12}>
<TabPanel>
<CmsSlot label="Description">{product.description}</CmsSlot>
<CmsSlot label="Specs">{product.specs}</CmsSlot>
</TabPanel>
</Grid>
<Grid item xs={12}>
<Lazy style={{ minHeight: 285 }}>
<SuggestedProducts product={product} />
</Lazy>
</Grid>
</form>
</Container>
</>
)
})
Example #21
Source File: ContactInfo.js From app with MIT License | 4 votes |
// This component assumes that the user has access to the given contact info and should not be used
// directly. Even though it assumes permissions, it also has a workaround for a timing issues
// related to permission when permission is granted on the request page.
function ContactDetails({ requestId }) {
const classes = useStyles();
const firestore = useFirestore();
const { showError } = useNotifications();
const [retries, setRetries] = useState(0);
const [contactInfo, setContactInfo] = useState(null);
const [accessFailed, setAccessFailed] = useState(false);
// This is used to trigger the snapshot subscription after confirming that the user
// has permission to access the contact info.
const [docRef, setDocRef] = useState(null);
// Because of timing issues, this component will likely get run before the server has applied
// the requested document access resulting in almost a guranteed permission-denied error. So,
// we use this effect to monitor for permission-denied until the change has propagated, at which
// point, we do the actual doc subscription (next useEffect);
useEffect(() => {
async function getData() {
try {
const ref = firestore.doc(
`${REQUESTS_CONTACT_INFO_COLLECTION}/${requestId}`,
);
// Call it once because this will throw the permission exception.
await ref.get();
setDocRef(ref); // Setting this will trigger the subscription useEffect.
} catch (err) {
// We only try reloading if insufficient permissions.
if (err.code !== 'permission-denied') {
throw err;
}
if (retries >= 25) {
setAccessFailed(true);
showError(
'Failed to get contact info access, please try again later.',
);
} else {
window.setTimeout(() => {
setRetries(retries + 1);
}, 1000);
}
}
}
getData();
}, [retries, firestore, requestId, showError]);
// Once the previous useEffect verifies that the user has access then this one does the actual
// document subscription.
useEffect(() => {
if (!docRef) return undefined;
const unsub = docRef.onSnapshot((docSnap) => {
setContactInfo(docSnap);
});
return unsub;
}, [docRef]);
if (!contactInfo) {
return <Skeleton />;
}
const phone = contactInfo.get('phone');
const email = contactInfo.get('email');
if (accessFailed) {
return <div className={classes.info}>Failed to get access.</div>;
}
return (
<div className={classes.info}>
<Typography variant="body2">
Phone: {phone ? <a href={`tel:${phone}`}>{phone}</a> : 'Not provided'}
</Typography>
<Typography variant="body2">
Email:{' '}
{email ? <a href={`mailto:${email}`}>{email}</a> : 'Not provided'}
</Typography>
{contactInfo.get('contactInfo') && (
<Typography variant="body2">
Contact Info: {contactInfo.get('contactInfo')}
</Typography>
)}
</div>
);
}
Example #22
Source File: NewsPost.jsx From archeage-tools with The Unlicense | 4 votes |
render() {
const { match: { params: { postId, action } }, mobile } = this.props;
if (action === 'edit') {
return <EditNewsPost {...this.props} />;
}
const { id, title, body, author, createDate, editDate, comments, commentCount, loading } = (this.state || this.props);
const isEdited = (editDate && editDate !== createDate);
const standalone = postId !== null;
const dateBlock = loading
? <Skeleton variant="text" width={60} />
: (
<OptionalTooltip
title={isEdited ? `Edited: ${new Date(editDate).toLocaleString(navigator.language || 'en-US')}` : null}
>
<Typography variant="overline" className={cn({ 'mark-tooltip': isEdited })}>
{moment(createDate).format('MMM DD, YYYY')}
</Typography>
</OptionalTooltip>
);
const authorBlock = loading
? <Skeleton variant="text" width={60} className={cn({ 'title-text': standalone })} />
: <Typography variant="subtitle2" className={cn({ 'title-text': !mobile, 'news-author': !mobile })}>
{mobile && 'Written '} by {mobile ? <Username user={author} /> : author}
</Typography>;
let titleNode = <Skeleton variant="text" width={180} />;
if (!loading) {
if (standalone) {
titleNode = title;
} else {
titleNode = <Link to={`/news/${id}`} color="inherit">{title}</Link>;
}
}
return (
<>
<div className="section">
<AppBar position="static">
<Toolbar variant="dense">
<Typography variant="h5" className={cn({ 'title-text': mobile })}>
{titleNode}
</Typography>
{!mobile && authorBlock}
{!mobile && dateBlock}
{!loading &&
<IfPerm permission="news.edit">
<Tooltip title="Edit Post">
<IconButton color="inherit" onClick={() => push(`/news/${id}/edit`)}>
<CreateIcon />
</IconButton>
</Tooltip>
</IfPerm>}
</Toolbar>
</AppBar>
{!loading &&
<Paper>
{/* <DraftJSRender contentState={stringToContentState(body)} />*/}
<Viewer value={body} />
{(!standalone || mobile) &&
<CardActions className="paper-action" disableSpacing>
{!standalone &&
<Button
startIcon={<ChatBubbleIcon />}
onClick={() => push(`/news/${id}#comments`)}
>
{commentCount}{!mobile && ' Comments'}
</Button>}
{mobile &&
(standalone
? <>
{authorBlock}
{dateBlock}
</>
: <div style={{ textAlign: 'right' }}>
{authorBlock}
{dateBlock}
</div>)}
</CardActions>}
</Paper>}
{loading &&
<Paper>
<div className="body-container">
<Skeleton variant="rect" width="100%" height={200} />
</div>
</Paper>}
</div>
{standalone && id && !loading &&
<Comments
postId={`NEWS-${id}`}
comments={comments}
commentCount={commentCount}
/>}
</>
);
}
Example #23
Source File: RequestPage.js From app with MIT License | 4 votes |
function RequestPage() {
const classes = useStyles();
const { requestId } = useParams();
const firestore = useFirestore();
const user = useUser();
const requestPublicSnap = useFirestoreDoc(
firestore.doc(`${REQUESTS_PUBLIC_COLLECTION}/${requestId}`),
);
if (!requestPublicSnap.exists) {
return (
<Container>
<Helmet>
<title>Request Not Found</title>
</Helmet>
<Paper className={classes.paper} data-test="request-not-found">
Request Not Found
</Paper>
</Container>
);
}
let immediacy = 0;
if (requestPublicSnap.exists) {
immediacy = parseInt(requestPublicSnap.get('d.immediacy'), 10);
}
const { latitude, longitude } =
requestPublicSnap.get('d.generalLocation') || {};
const generalLocationName =
requestPublicSnap.get('d.generalLocationName') || '';
const mapImage = (
<img
className={classes.map}
alt={requestPublicSnap.get('d.generalLocationName')}
title={requestPublicSnap.get('d.generalLocationName')}
src={`https://maps.googleapis.com/maps/api/staticmap?key=${process.env.REACT_APP_FIREBASE_API_KEY}¢er=${latitude},${longitude}&markers=${latitude},${longitude}&size=280x280&zoom=10`}
/>
);
return (
<>
<Helmet>
<title>
{requestPublicSnap.get('d.firstName') || ''} –{' '}
{generalLocationName}
</title>
</Helmet>
<Container
className={classes.header}
data-test="request-title"
maxWidth={false}>
<Container>
<Typography variant="h5" gutterBottom>
{requestPublicSnap.get('d.firstName')}{' '}
{/* {user &&
user.uid &&
requestPublicSnap.get('d.owner') === user.uid &&
`${requestPublicSnap.get('d.lastName')} `} */}
– {generalLocationName}
</Typography>
</Container>
</Container>
<Container className={classes.bodyContainer}>
<Paper className={classes.paper} data-test="request-info">
<Hidden smUp>
<div className={classes.mobileImageContainer}>{mapImage}</div>
</Hidden>
<div className={classes.basicInfoContainer}>
<div className={classes.basicInfo}>
<Typography variant="h6" gutterBottom>
{immediacy === 1
? 'Not urgent'
: immediacy <= 5
? 'Not very urgent'
: 'URGENT'}
:{' '}
{requestPublicSnap.get('d.needs') &&
requestPublicSnap
.get('d.needs')
.map((item) => (
<React.Fragment key={item}>
{allCategoryMap[item] ? (
<Chip
label={allCategoryMap[item].shortDescription}
className={classes.needChip}
/>
) : (
<Alert severity="error">
Could not find '{item}' in all category
map.
</Alert>
)}
</React.Fragment>
))}
</Typography>
<Typography variant="caption" gutterBottom>
REQUESTED
</Typography>
<Typography variant="h6" gutterBottom>
{requestPublicSnap.get('d.createdAt') &&
format(
requestPublicSnap.get('d.createdAt').toDate(),
'EEE, MMM d, yyyy h:mm a',
)}
</Typography>
<Typography variant="caption">CONTACT</Typography>
<Typography variant="h6" gutterBottom>
<ContactInfo requestId={requestId} />
</Typography>
<Typography variant="caption" gutterBottom>
OTHER DETAILS
</Typography>
{requestPublicSnap.get('d.otherDetails') ? (
<Typography variant="h6" gutterBottom>
{requestPublicSnap.get('d.otherDetails')}
</Typography>
) : (
<Box color="text.disabled">
<Typography
variant="body2"
gutterBottom
className={classes.noDetails}>
No other details provided.
</Typography>
</Box>
)}
</div>
<Hidden xsDown>{mapImage}</Hidden>
</div>
<Divider className={classes.divider} />
<Suspense fallback={<Skeleton />}>
<RequestActions requestPublicSnapshot={requestPublicSnap} />
</Suspense>
</Paper>
<Paper className={classes.paper} data-test="public-comments">
<PublicComments requestId={requestId} />
</Paper>
{/* Only show for authenticated users. */}
{user && user.uid && <Discussion requestId={requestId} />}
</Container>
</>
);
}