rewrite home page, add useBasicSearch

This commit is contained in:
Sidney Alcantara
2021-09-06 20:04:08 +10:00
parent 261f4214e1
commit 6c92dff0d4
34 changed files with 1078 additions and 678 deletions

View File

@@ -53,9 +53,9 @@
"react-joyride": "^2.3.0",
"react-json-view": "^1.19.1",
"react-router-dom": "^5.0.1",
"react-router-hash-link": "^2.4.3",
"react-scripts": "^4.0.3",
"react-scroll-sync": "^0.8.0",
"react-use-search": "^0.3.1",
"react-usestateref": "^1.0.5",
"serve": "^11.3.2",
"tinymce": "^5.2.0",
@@ -106,6 +106,7 @@
"@types/react-div-100vh": "^0.3.0",
"@types/react-dom": "^17.0.8",
"@types/react-router-dom": "^5.1.7",
"@types/react-router-hash-link": "^2.4.1",
"@types/use-persisted-state": "^0.3.0",
"craco-alias": "^3.0.1",
"firebase-tools": "^8.12.1",

View File

@@ -1,76 +0,0 @@
import { Link } from "react-router-dom";
import { makeStyles, createStyles } from "@material-ui/styles";
import {
useTheme,
useScrollTrigger,
AppBar as MuiAppBar,
Toolbar,
Grid,
Button,
} from "@material-ui/core";
import Logo from "assets/Logo";
import routes from "constants/routes";
const useStyles = makeStyles((theme) =>
createStyles({
appBar: {
backgroundColor: theme.palette.background.paper,
marginBottom: theme.spacing(6),
},
logo: {
display: "block",
marginRight: theme.spacing(1),
},
heading: {
textTransform: "none",
color: theme.palette.primary.main,
cursor: "default",
userSelect: "none",
fontFeatureSettings: '"liga"',
},
locationDropdown: {
minWidth: 140,
margin: 0,
},
})
);
interface IAppBarProps {}
const AppBar: React.FunctionComponent<IAppBarProps> = () => {
const classes = useStyles();
const theme = useTheme();
const trigger = useScrollTrigger({ disableHysteresis: true, threshold: 0 });
return (
<MuiAppBar
position="sticky"
color="default"
className={classes.appBar}
elevation={trigger ? 4 : 0}
>
<Toolbar>
<Grid item xs>
<Logo />
</Grid>
<Grid item>
<Button
component={Link}
to={routes.signOut}
color="primary"
variant="outlined"
>
Sign Out
</Button>
</Grid>
</Toolbar>
</MuiAppBar>
);
};
export default AppBar;

View File

@@ -7,12 +7,16 @@ import {
} from "@material-ui/core";
import SearchIcon from "@material-ui/icons/Search";
import { APP_BAR_HEIGHT } from "components/Navigation";
export interface IFloatingSearchProps extends Partial<FilledTextFieldProps> {
label: string;
paperSx?: FilledTextFieldProps["sx"];
}
export default function FloatingSearch({
label,
paperSx,
...props
}: IFloatingSearchProps) {
const trigger = useScrollTrigger({ disableHysteresis: true, threshold: 0 });
@@ -22,8 +26,9 @@ export default function FloatingSearch({
elevation={trigger ? 8 : 1}
sx={{
position: "sticky",
top: (theme) => theme.spacing(7 + 1),
top: (theme) => theme.spacing(APP_BAR_HEIGHT / 8 + 1),
zIndex: "appBar",
...paperSx,
}}
>
<TextField
@@ -34,6 +39,7 @@ export default function FloatingSearch({
type="search"
id="user-management-search"
size="medium"
autoFocus
InputProps={{
startAdornment: (
<InputAdornment
@@ -46,16 +52,32 @@ export default function FloatingSearch({
}}
sx={{
"& .MuiInputLabel-root": {
height: "0px",
m: 0,
p: 0,
pointerEvents: "none",
opacity: 0,
mt: -5,
mb: 2,
},
"& .MuiFilledInput-root": {
boxShadow: 0,
borderRadius: 2,
"&::before": {
borderRadius: 2,
height: (theme) => (theme.shape.borderRadius as number) * 4,
boxShadow: (theme) =>
`0 -1px 0 0 ${theme.palette.text.disabled} inset`,
"&:hover": {
boxShadow: (theme) =>
`0 -1px 0 0 ${theme.palette.text.primary} inset`,
},
"&.Mui-focused, &.Mui-focused:hover": {
boxShadow: (theme) =>
`0 -2px 0 0 ${theme.palette.primary.main} inset`,
},
"&::after": {
width: (theme) =>
`calc(100% - ${
(theme.shape.borderRadius as number) * 2 * 2
}px)`,
left: (theme) => (theme.shape.borderRadius as number) * 2,
},
},
}}

View File

@@ -0,0 +1,51 @@
import { Link } from "react-router-dom";
import { Typography, Link as MuiLink, Button } from "@material-ui/core";
import SecurityIcon from "@material-ui/icons/SecurityOutlined";
import EmptyState from "components/EmptyState";
import WIKI_LINKS from "constants/wikiLinks";
import routes from "constants/routes";
export default function AccessDenied() {
return (
<EmptyState
fullScreen
Icon={SecurityIcon}
message="Access Denied"
description={
<>
<Typography>
You do not have access to this project. Please contact the project
owner.
</Typography>
<Typography>
If you are the project owner, please follow{" "}
<MuiLink
href={WIKI_LINKS.securityRules}
target="_blank"
rel="noopener noreferrer"
>
these instructions
</MuiLink>{" "}
to set up the projects security rules.
</Typography>
<Button component={Link} to={routes.signOut}>
Sign Out
</Button>
</>
}
sx={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
bgcolor: "background.default",
zIndex: 9999,
}}
/>
);
}

View File

@@ -2,13 +2,7 @@ import { Zoom, Stack, Typography } from "@material-ui/core";
export default function HomeWelcomePrompt() {
return (
<Zoom
in
style={{
transformOrigin: `${320 - 52}px ${320 - 52}px`,
transitionDelay: "3s",
}}
>
<Zoom in style={{ transformOrigin: `${320 - 52}px ${320 - 52}px` }}>
<Stack
justifyContent="center"
sx={{

View File

@@ -0,0 +1,76 @@
import { Link } from "react-router-dom";
import {
Card,
CardActionArea,
CardContent,
Typography,
CardActions,
Button,
} from "@material-ui/core";
import GoIcon from "assets/icons/Go";
import { Table } from "contexts/RowyContext";
export interface ITableCardProps extends Table {
link: string;
actions?: React.ReactNode;
}
export default function TableCard({
section,
name,
description,
link,
actions,
}: ITableCardProps) {
return (
<Card style={{ height: "100%", display: "flex", flexDirection: "column" }}>
<CardActionArea
sx={{
flexGrow: 1,
display: "flex",
alignItems: "flex-start",
justifyContent: "flex-start",
borderRadius: 2,
}}
component={Link}
to={link}
>
<CardContent>
<Typography variant="overline" component="p">
{section}
</Typography>
<Typography variant="h6" component="h3" gutterBottom>
{name}
</Typography>
<Typography
color="textSecondary"
sx={{
minHeight: (theme) =>
(theme.typography.body2.lineHeight as number) * 2 + "em",
}}
>
{description}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button
variant="text"
color="primary"
endIcon={<GoIcon />}
component={Link}
to={link}
>
Open
</Button>
<div style={{ flexGrow: 1 }} />
{actions}
</CardActions>
</Card>
);
}

View File

@@ -0,0 +1,42 @@
import {
Card,
CardContent,
Typography,
CardActions,
Skeleton,
} from "@material-ui/core";
export default function TableCardSkeleton() {
return (
<Card style={{ height: "100%", display: "flex", flexDirection: "column" }}>
<CardContent style={{ flexGrow: 1 }}>
<Typography variant="overline">
<Skeleton width={80} />
</Typography>
<Typography variant="h6" gutterBottom>
<Skeleton width={180} />
</Typography>
<Typography
color="textSecondary"
sx={{
minHeight: (theme) =>
(theme.typography.body2.lineHeight as number) * 2 + "em",
}}
>
<Skeleton width={120} />
</Typography>
</CardContent>
<CardActions sx={{ mb: 1, mx: 1 }}>
<Skeleton
width={60}
height={20}
variant="rectangular"
sx={{ borderRadius: 1, mr: "auto" }}
/>
<Skeleton variant="circular" width={24} height={24} />
</CardActions>
</Card>
);
}

View File

@@ -0,0 +1,48 @@
import { Container, Paper, Box, Grid } from "@material-ui/core";
import SectionHeadingSkeleton from "components/SectionHeadingSkeleton";
import TableCardSkeleton from "./TableCardSkeleton";
export default function TableGridSkeleton() {
return (
<Container component="main" sx={{ px: 1, pt: 1, pb: 7 + 3 + 3 }}>
<Paper
sx={{
height: 48,
maxWidth: (theme) => theme.breakpoints.values.sm - 48,
width: { xs: "100%", md: "50%", lg: "100%" },
mx: "auto",
}}
/>
<Box component="section" sx={{ mt: 4 }}>
<SectionHeadingSkeleton sx={{ pl: 2, pr: 1 }} />
<Grid container spacing={2}>
<Grid item xs={12} sm={6} md={4} lg={3}>
<TableCardSkeleton />
</Grid>
<Grid item xs={12} sm={6} md={4} lg={3}>
<TableCardSkeleton />
</Grid>
<Grid item xs={12} sm={6} md={4} lg={3}>
<TableCardSkeleton />
</Grid>
</Grid>
</Box>
<Box component="section" sx={{ mt: 4 }}>
<SectionHeadingSkeleton sx={{ pl: 2, pr: 1 }} />
<Grid container spacing={2}>
<Grid item xs={12} sm={6} md={4} lg={3}>
<TableCardSkeleton />
</Grid>
<Grid item xs={12} sm={6} md={4} lg={3}>
<TableCardSkeleton />
</Grid>
<Grid item xs={12} sm={6} md={4} lg={3}>
<TableCardSkeleton />
</Grid>
</Grid>
</Box>
</Container>
);
}

View File

@@ -0,0 +1,73 @@
import { TransitionGroup } from "react-transition-group";
import { Box, Grid, Collapse } from "@material-ui/core";
import SectionHeading from "components/SectionHeading";
import TableCard from "./TableCard";
import SlideTransition from "components/Modal/SlideTransition";
import { Table } from "contexts/RowyContext";
export interface ITableGridProps {
sections: Record<string, Table[]>;
getLink: (table: Table) => string;
getActions?: (table: Table) => React.ReactNode;
}
export default function TableGrid({
sections,
getLink,
getActions,
}: ITableGridProps) {
return (
<TransitionGroup>
{Object.entries(sections).map(
([sectionName, sectionTables], sectionIndex) => {
const tableItems = sectionTables
.map((table, tableIndex) => {
if (!table) return null;
return (
<SlideTransition
key={table.id}
appear
timeout={(sectionIndex + 1) * 100 + tableIndex * 50}
>
<Grid item xs={12} sm={6} md={4} lg={3}>
<TableCard
{...table}
link={getLink(table)}
actions={getActions ? getActions(table) : null}
/>
</Grid>
</SlideTransition>
);
})
.filter((item) => item !== null);
if (tableItems.length === 0) return null;
return (
<Collapse key={sectionName}>
<Box component="section" sx={{ mt: 4 }}>
<SlideTransition
key={"grid-section-" + sectionName}
in
timeout={(sectionIndex + 1) * 100}
>
<SectionHeading sx={{ pl: 2, pr: 1.5 }}>
{sectionName}
</SectionHeading>
</SlideTransition>
<Grid component={TransitionGroup} container spacing={2}>
{tableItems}
</Grid>
</Box>
</Collapse>
);
}
)}
</TransitionGroup>
);
}

View File

@@ -0,0 +1,78 @@
import { Link } from "react-router-dom";
import {
ListItem,
ListItemButton,
Typography,
IconButton,
} from "@material-ui/core";
import GoIcon from "@material-ui/icons/ArrowForward";
import { Table } from "contexts/RowyContext";
export interface ITableListItemProps extends Table {
link: string;
actions?: React.ReactNode;
}
export default function TableListItem({
// section,
name,
description,
link,
actions,
}: ITableListItemProps) {
return (
<ListItem disableGutters disablePadding>
<ListItemButton
component={Link}
to={link}
sx={{
alignItems: "baseline",
height: 48,
py: 0,
pr: 0,
borderRadius: 2,
"& > *": { lineHeight: "48px !important" },
flexWrap: "nowrap",
overflow: "hidden",
}}
>
{/* <Typography
variant="overline"
component="p"
noWrap
color="textSecondary"
sx={{ maxWidth: 100, flexShrink: 0, flexGrow: 1, mr: 2 }}
>
{section}
</Typography> */}
<Typography
component="h3"
variant="button"
noWrap
sx={{ maxWidth: 160, flexShrink: 0, flexGrow: 1, mr: 2 }}
>
{name}
</Typography>
<Typography color="textSecondary" noWrap>
{description}
</Typography>
</ListItemButton>
<div style={{ flexShrink: 0 }}>
{actions}
<IconButton
size="large"
color="primary"
component={Link}
to={link}
sx={{ display: { xs: "none", sm: "inline-flex" } }}
>
<GoIcon />
</IconButton>
</div>
</ListItem>
);
}

View File

@@ -0,0 +1,23 @@
import { ListItem, Skeleton } from "@material-ui/core";
export default function TableListItemSkeleton() {
return (
<ListItem disableGutters disablePadding style={{ height: 48 }}>
<Skeleton width={160} sx={{ mx: 2, flexShrink: 0 }} />
<Skeleton sx={{ mr: 2, flexBasis: 240, flexShrink: 1 }} />
<Skeleton
variant="circular"
width={24}
height={24}
sx={{ ml: "auto", mr: 3, flexShrink: 0 }}
/>
<Skeleton
variant="circular"
width={24}
height={24}
sx={{ mr: 1.5, flexShrink: 0 }}
/>
</ListItem>
);
}

View File

@@ -0,0 +1,36 @@
import { Container, Box, Paper } from "@material-ui/core";
import SectionHeadingSkeleton from "components/SectionHeadingSkeleton";
import TableListItemSkeleton from "./TableListItemSkeleton";
export default function TableGridSkeleton() {
return (
<Container component="main" sx={{ px: 1, pt: 1, pb: 7 + 3 + 3 }}>
<Paper
sx={{
height: 48,
maxWidth: (theme) => theme.breakpoints.values.sm - 48,
width: { xs: "100%", md: "50%", lg: "100%" },
mx: "auto",
}}
/>
<Box component="section" sx={{ mt: 4 }}>
<SectionHeadingSkeleton sx={{ pl: 2, pr: 1 }} />
<Paper>
<TableListItemSkeleton />
<TableListItemSkeleton />
<TableListItemSkeleton />
</Paper>
</Box>
<Box component="section" sx={{ mt: 4 }}>
<SectionHeadingSkeleton sx={{ pl: 2, pr: 1 }} />
<Paper>
<TableListItemSkeleton />
<TableListItemSkeleton />
<TableListItemSkeleton />
</Paper>
</Box>
</Container>
);
}

View File

@@ -0,0 +1,71 @@
import { TransitionGroup } from "react-transition-group";
import { Box, Paper, Collapse, List } from "@material-ui/core";
import SectionHeading from "components/SectionHeading";
import TableListItem from "./TableListItem";
import SlideTransition from "components/Modal/SlideTransition";
import { Table } from "contexts/RowyContext";
export interface ITableListProps {
sections: Record<string, Table[]>;
getLink: (table: Table) => string;
getActions?: (table: Table) => React.ReactNode;
}
export default function TableList({
sections,
getLink,
getActions,
}: ITableListProps) {
return (
<TransitionGroup>
{Object.entries(sections).map(
([sectionName, sectionTables], sectionIndex) => {
const tableItems = sectionTables
.map((table) => {
if (!table) return null;
return (
<Collapse key={table.id}>
<TableListItem
{...table}
link={getLink(table)}
actions={getActions ? getActions(table) : null}
/>
</Collapse>
);
})
.filter((item) => item !== null);
if (tableItems.length === 0) return null;
return (
<Collapse key={sectionName}>
<Box component="section" sx={{ mt: 4 }}>
<SlideTransition
key={"list-section-" + sectionName}
in
timeout={(sectionIndex + 1) * 100}
>
<SectionHeading sx={{ pl: 2, pr: 1 }}>
{sectionName}
</SectionHeading>
</SlideTransition>
<SlideTransition in timeout={(sectionIndex + 1) * 100}>
<Paper>
<List disablePadding>
<TransitionGroup>{tableItems}</TransitionGroup>
</List>
</Paper>
</SlideTransition>
</Box>
</Collapse>
);
}
)}
</TransitionGroup>
);
}

View File

@@ -14,11 +14,11 @@ export const SlideTransition: React.ForwardRefExoticComponent<
const defaultStyle = {
opacity: 0,
transform: "translateY(16px)",
transform: "translateY(40px)",
transition: theme.transitions.create(["transform", "opacity"], {
duration: "300ms",
easing: "cubic-bezier(0.075, 0.82, 0.165, 1)",
easing: "cubic-bezier(0.1, 0.8, 0.1, 1)",
}),
};

View File

@@ -10,6 +10,7 @@ export default function NavItem(props: MenuItemProps<Link>) {
selected={pathname === props.to}
{...props}
sx={{
...props.sx,
"&&::before": {
left: "auto",
right: 0,

View File

@@ -54,8 +54,12 @@ export default function NavDrawerItem({
)}`
}
onClick={closeDrawer}
sx={{
ml: 2,
width: (theme) => `calc(100% - ${theme.spacing(2 + 0.5)})`,
}}
>
<ListItemText primary={table.name} sx={{ pl: 2 }} />
<ListItemText primary={table.name} />
</NavItem>
</li>
))}

View File

@@ -33,7 +33,7 @@ export default function UserMenu(props: IconButtonProps) {
setThemeOverridden,
} = useAppContext();
if (!currentUser || !userDoc || !userDoc?.state?.doc)
return <div style={{ width: 48, height: 48 }} />;
return <div style={{ width: 48 - 12, height: 48 }} />;
const displayName = userDoc?.state?.doc?.user?.displayName;
const avatarUrl = userDoc?.state?.doc?.user?.photoURL;

View File

@@ -52,10 +52,11 @@ const useStyles = makeStyles((theme) =>
margin: 1,
},
"& .tox-toolbar-overlord, & .tox-edit-area__iframe, & .tox-toolbar__primary": {
background: "transparent",
borderRadius: (theme.shape.borderRadius as number) - 1,
},
"& .tox-toolbar-overlord, & .tox-edit-area__iframe, & .tox-toolbar__primary":
{
background: "transparent",
borderRadius: (theme.shape.borderRadius as number) - 1,
},
"& .tox-toolbar__primary": { padding: theme.spacing(0.5, 0) },
"& .tox-toolbar__group": {

View File

@@ -0,0 +1,70 @@
import { forwardRef } from "react";
import _camelCase from "lodash/camelCase";
import { HashLink } from "react-router-hash-link";
import { Stack, StackProps, Typography, IconButton } from "@material-ui/core";
import LinkIcon from "@material-ui/icons/Link";
import { APP_BAR_HEIGHT } from "components/Navigation";
export interface ISectionHeadingProps extends Omit<StackProps, "children"> {
children: string;
}
export const SectionHeading = forwardRef(function SectionHeading_(
{ children, sx, ...props }: ISectionHeadingProps,
ref
) {
const sectionLink = _camelCase(children);
return (
<Stack
ref={ref}
direction="row"
alignItems="flex-end"
id={sectionLink}
{...props}
sx={{
pb: 0.5,
cursor: "default",
...sx,
position: "relative",
zIndex: 1,
"&:hover .sectionHeadingLink, &:active .sectionHeadingLink": {
opacity: 1,
},
scrollMarginTop: (theme) => theme.spacing(APP_BAR_HEIGHT / 8 + 3.5),
scrollBehavior: "smooth",
}}
>
<Typography variant="subtitle1" component="h2">
{children}
</Typography>
<IconButton
component={HashLink}
to={`#${sectionLink}`}
smooth
size="small"
className="sectionHeadingLink"
sx={{
my: -0.5,
ml: 1,
opacity: 0,
transition: (theme) =>
theme.transitions.create("opacity", {
duration: theme.transitions.duration.short,
}),
"&:focus": { opacity: 1 },
}}
>
<LinkIcon />
</IconButton>
</Stack>
);
});
export default SectionHeading;

View File

@@ -0,0 +1,14 @@
import { Stack, StackProps, Skeleton } from "@material-ui/core";
export default function SectionHeadingSkeleton({ sx, ...props }: StackProps) {
return (
<Stack
direction="row"
alignItems="flex-end"
{...props}
sx={{ pb: 0.5, ...sx }}
>
<Skeleton width={120} />
</Stack>
);
}

View File

@@ -1,36 +1,43 @@
import { Typography, Paper } from "@material-ui/core";
import { Paper, PaperProps } from "@material-ui/core";
import SectionHeading from "components/SectionHeading";
import SlideTransition from "components/Modal/SlideTransition";
export interface ISettingsSectionProps {
children: React.ReactNode;
title: string;
paperSx?: PaperProps["sx"];
transitionTimeout?: number;
}
export default function SettingsSection({
children,
title,
paperSx,
transitionTimeout = 100,
}: ISettingsSectionProps) {
return (
<section style={{ cursor: "default" }}>
<Typography
variant="subtitle1"
component="h2"
sx={{ mx: 1, mb: 0.5 }}
id={title}
>
{title}
</Typography>
<Paper
sx={{
p: { xs: 2, sm: 3 },
<SlideTransition in timeout={transitionTimeout}>
<SectionHeading sx={{ mx: 1 }}>{title}</SectionHeading>
</SlideTransition>
"& > :not(style) + :not(style)": {
m: 0,
mt: { xs: 2, sm: 3 },
},
}}
>
{children}
</Paper>
<SlideTransition in timeout={transitionTimeout + 50}>
<Paper
sx={{
p: { xs: 2, sm: 3 },
"& > :not(style) + :not(style)": {
m: 0,
mt: { xs: 2, sm: 3 },
},
...paperSx,
}}
>
{children}
</Paper>
</SlideTransition>
</section>
);
}

View File

@@ -0,0 +1,81 @@
import { useState } from "react";
import { Link } from "react-router-dom";
import {
Tooltip,
Zoom,
Fab,
DialogContentText,
Link as MuiLink,
TextField,
} from "@material-ui/core";
import AddIcon from "@material-ui/icons/PersonAddOutlined";
import Modal from "components/Modal";
import routes from "constants/routes";
export default function InviteUser() {
const [open, setOpen] = useState(false);
return (
<>
<Tooltip title="Invite User">
<Zoom in>
<Fab
aria-label="Invite User"
onClick={() => setOpen(true)}
color="secondary"
sx={{
zIndex: "speedDial",
position: "fixed",
bottom: (theme) => ({
xs: theme.spacing(2),
sm: theme.spacing(3),
}),
right: (theme) => ({
xs: theme.spacing(2),
sm: theme.spacing(3),
}),
}}
>
<AddIcon />
</Fab>
</Zoom>
</Tooltip>
{open && (
<Modal
title="Invite User"
onClose={() => setOpen(false)}
maxWidth="xs"
body={
<>
<DialogContentText paragraph>
Send an email to this user to invite them to join your project.
</DialogContentText>
<DialogContentText paragraph>
They can sign up with any of the sign-in options{" "}
<MuiLink
component={Link}
to={routes.projectSettings + "#authentication"}
>
you have enabled
</MuiLink>
, as long as they use the same email address.
</DialogContentText>
<TextField
label="Email Address"
id="invite-email"
fullWidth
autoFocus
placeholder="name@example.com"
/>
</>
}
actions={{ primary: { children: "Invite" } }}
/>
)}
</>
);
}

View File

@@ -26,12 +26,9 @@ export default function UserItem({
<ListItemText
primary={displayName}
secondary={email}
primaryTypographyProps={{
style: { userSelect: "all" },
}}
secondaryTypographyProps={{
noWrap: true,
style: { userSelect: "all" },
sx={{
overflowX: "hidden",
"& > *": { userSelect: "all" },
}}
/>
</>
@@ -55,6 +52,7 @@ export default function UserItem({
},
"& .MuiFilledInput-root": {
bgcolor: "transparent",
boxShadow: 0,
"&::before": { content: "none" },
@@ -63,7 +61,7 @@ export default function UserItem({
"& .MuiSelect-select.MuiFilledInput-input": {
typography: "button",
pl: 1,
pr: 3.5,
pr: 3.25,
},
"& .MuiSelect-icon": {
right: 2,

View File

@@ -1,181 +0,0 @@
import clsx from "clsx";
import { Link, LinkProps } from "react-router-dom";
import { makeStyles, createStyles } from "@material-ui/styles";
import {
Card,
Grid,
Typography,
Button,
CardActions,
CardContent,
CardMedia,
Divider,
} from "@material-ui/core";
import { ButtonProps } from "@material-ui/core/Button";
import GoIcon from "assets/icons/Go";
const useStyles = makeStyles((theme) =>
createStyles({
root: { width: "100%" },
container: { height: "100%" },
cardContent: { "&:last-child": { paddingBottom: 0 } },
headerSection: { marginBottom: theme.spacing(1) },
overline: {
marginBottom: theme.spacing(2),
color: theme.palette.text.secondary,
},
title: { whiteSpace: "pre-line" },
image: {
width: 80,
height: 80,
borderRadius: theme.shape.borderRadius,
},
cardActions: {
// padding: theme.spacing(1),
display: "flex",
justifyContent: "space-between",
},
divider: {
margin: theme.spacing(2),
marginBottom: 0,
},
})
);
interface StyledCardProps {
className?: string;
overline?: React.ReactNode;
title?: string;
imageSource?: string;
bodyContent?: React.ReactNode;
primaryButton?: Partial<ButtonProps>;
primaryLink?: {
to: LinkProps["to"];
children?: React.ReactNode;
label?: string;
};
secondaryAction?: React.ReactNode;
headerAction?: React.ReactNode;
}
export default function StyledCard({
className,
overline,
title,
imageSource,
bodyContent,
primaryButton,
primaryLink,
secondaryAction,
headerAction,
}: StyledCardProps) {
const classes = useStyles();
return (
<Card className={clsx(className, classes.root)}>
<Grid
container
direction="column"
wrap="nowrap"
className={classes.container}
>
<Grid item xs>
<CardContent className={clsx(classes.container, classes.cardContent)}>
<Grid
container
direction="column"
wrap="nowrap"
className={classes.container}
>
<Grid item>
<Grid container className={classes.headerSection} spacing={3}>
<Grid item xs>
{overline && (
<Typography
variant="overline"
className={classes.overline}
>
{overline}
</Typography>
)}
<Grid
container
direction="row"
justifyContent="space-between"
>
{title && (
<Typography variant="h5" className={classes.title}>
{title}
</Typography>
)}
{headerAction && headerAction}
</Grid>
</Grid>
{imageSource && (
<Grid item>
<CardMedia
className={classes.image}
image={imageSource}
title={title}
/>
</Grid>
)}
</Grid>
</Grid>
<Grid item xs>
{bodyContent && Array.isArray(bodyContent) ? (
<Grid
container
direction="column"
wrap="nowrap"
justifyContent="space-between"
>
{bodyContent.map((element) => (
<Grid item>{element}</Grid>
))}
</Grid>
) : (
<Typography variant="body2" color="textSecondary">
{bodyContent}
</Typography>
)}
</Grid>
</Grid>
</CardContent>
</Grid>
<Grid item>
<Divider className={classes.divider} />
<CardActions className={classes.cardActions}>
{primaryButton && (
<Button color="primary" variant="text" {...primaryButton} />
)}
{primaryLink && (
<Button
color="primary"
variant="text"
component={Link as any}
to={primaryLink.to}
children={primaryLink.children || primaryLink.label}
endIcon={<GoIcon />}
/>
)}
{secondaryAction}
</CardActions>
</Grid>
</Grid>
</Card>
);
}

View File

@@ -53,7 +53,7 @@ export const AppProvider: React.FC = ({ children }) => {
}, []);
useEffect(() => {
document.title = `${projectId} | ${name}`;
document.title = `${projectId} ${name}`;
}, []);
// Store matching userDoc

View File

@@ -13,6 +13,7 @@ import { ImportWizardRef } from "components/Wizards/ImportWizard";
import _find from "lodash/find";
import { deepen } from "utils/fns";
export type Table = {
id: string;
collection: string;
name: string;
roles: string[];
@@ -111,7 +112,9 @@ export const RowyContextProvider: React.FC = ({ children }) => {
const _sections = _groupBy(filteredTables, "section");
setSections(_sections);
setTables(filteredTables);
setTables(
filteredTables.map((table) => ({ ...table, id: table.collection }))
);
}
}, [settings, userRoles, sections]);

View File

@@ -0,0 +1,17 @@
import { useState } from "react";
import { useDebouncedCallback } from "use-debounce";
export default function useBasicSearch<T>(
collection: T[],
predicate: (item: T, query: string) => boolean,
debounce: number = 400
) {
const [query, setQuery] = useState("");
const [handleQuery] = useDebouncedCallback(setQuery, debounce);
const results = query
? collection.filter((user) => predicate(user, query.toLowerCase()))
: collection;
return [results, query, handleQuery] as const;
}

View File

@@ -1,104 +1,75 @@
import { useState, useEffect } from "react";
import queryString from "query-string";
import { Link } from "react-router-dom";
import { useState, ChangeEvent } from "react";
import createPersistedState from "use-persisted-state";
import _groupBy from "lodash/groupBy";
import _find from "lodash/find";
import { makeStyles, createStyles } from "@material-ui/styles";
import {
Container,
Grid,
Stack,
Typography,
Divider,
ToggleButtonGroup,
ToggleButton,
Tooltip,
Fab,
Checkbox,
Tooltip,
IconButton,
Link as MuiLink,
Button,
Zoom,
} from "@material-ui/core";
import ViewListIcon from "@material-ui/icons/ViewListOutlined";
import ViewGridIcon from "@material-ui/icons/ViewModuleOutlined";
import FavoriteBorderIcon from "@material-ui/icons/FavoriteBorder";
import FavoriteIcon from "@material-ui/icons/Favorite";
import EditIcon from "@material-ui/icons/EditOutlined";
import AddIcon from "@material-ui/icons/Add";
import EditIcon from "@material-ui/icons/Edit";
import Favorite from "@material-ui/icons/Favorite";
import FavoriteBorder from "@material-ui/icons/FavoriteBorder";
import SecurityIcon from "@material-ui/icons/SecurityOutlined";
import StyledCard from "components/StyledCard";
import HomeWelcomePrompt from "components/HomeWelcomePrompt";
import EmptyState from "components/EmptyState";
import FloatingSearch from "components/FloatingSearch";
import TableGrid from "components/Home/TableGrid";
import TableList from "components/Home/TableList";
import TableGridSkeleton from "components/Home/TableGrid/TableGridSkeleton";
import TableListSkeleton from "components/Home/TableList/TableListSkeleton";
import HomeWelcomePrompt from "components/Home/HomeWelcomePrompt";
import AccessDenied from "components/Home/AccessDenied";
import routes from "constants/routes";
import { useRowyContext } from "contexts/RowyContext";
import { useAppContext } from "contexts/AppContext";
import { useRowyContext, Table } from "contexts/RowyContext";
import useDoc, { DocActions } from "hooks/useDoc";
import useBasicSearch from "hooks/useBasicSearch";
import TableSettingsDialog, {
TableSettingsDialogModes,
} from "components/TableSettings";
import WIKI_LINKS from "constants/wikiLinks";
import { SETTINGS } from "config/dbPaths";
const useStyles = makeStyles((theme) =>
createStyles({
"@global": {
html: { scrollBehavior: "smooth" },
},
root: {
minHeight: "100vh",
paddingBottom: theme.spacing(8),
},
section: {
paddingTop: theme.spacing(10),
"&:first-of-type": { marginTop: theme.spacing(2) },
},
sectionHeader: {
color: theme.palette.text.secondary,
},
divider: { margin: theme.spacing(1, 0, 3) },
cardGrid: {
[theme.breakpoints.down("sm")]: { maxWidth: 360, margin: "0 auto" },
},
card: {
height: "100%",
[theme.breakpoints.up("md")]: { minHeight: 220 },
[theme.breakpoints.down("lg")]: { minHeight: 180 },
},
favButton: {
margin: theme.spacing(-0.5, -1, 0, 0),
},
configFab: {
position: "fixed",
bottom: theme.spacing(3),
right: theme.spacing(12),
},
fab: {
position: "fixed",
bottom: theme.spacing(3),
right: theme.spacing(3),
},
})
);
const useHomeViewState = createPersistedState("__ROWY__HOME_VIEW");
export default function HomePage() {
const classes = useStyles();
const { userDoc } = useAppContext();
const { tables, userClaims } = useRowyContext();
const [results, query, handleQuery] = useBasicSearch(
tables ?? [],
(table, query) =>
table.id.toLowerCase().includes(query) ||
table.name.toLowerCase().includes(query) ||
table.section.toLowerCase().includes(query) ||
table.description.toLowerCase().includes(query)
);
const [view, setView] = useHomeViewState("grid");
const favorites = Array.isArray(userDoc.state.doc?.favoriteTables)
? userDoc.state.doc.favoriteTables
: [];
const sections = {
Favorites: favorites.map((id) => _find(results, { id })),
..._groupBy(results, "section"),
};
const [settingsDialogState, setSettingsDialogState] = useState<{
mode: null | TableSettingsDialogModes;
data: null | {
collection: string;
description: string;
roles: string[];
name: string;
section: string;
isCollectionGroup: boolean;
tableType: string;
};
}>({
mode: null,
data: null,
});
data: null | (Table & { tableType: string });
}>({ mode: null, data: null });
const clearDialog = () =>
setSettingsDialogState({
@@ -106,228 +77,170 @@ export default function HomePage() {
data: null,
});
useEffect(() => {
const modal = decodeURIComponent(
queryString.parse(window.location.search).modal as string
);
if (modal) {
switch (modal) {
case "settings":
setOpenProjectSettings(true);
break;
default:
break;
}
}
}, [window.location.search]);
const { sections } = useRowyContext();
const { userDoc } = useAppContext();
const favs = userDoc.state.doc?.favoriteTables
? userDoc.state.doc.favoriteTables
: [];
const handleCreateTable = () =>
setSettingsDialogState({
mode: TableSettingsDialogModes.create,
data: null,
});
const [openProjectSettings, setOpenProjectSettings] = useState(false);
const [openBuilderInstaller, setOpenBuilderInstaller] = useState(false);
const [settingsDocState, settingsDocDispatch] = useDoc({ path: SETTINGS });
useEffect(() => {
if (!settingsDocState.loading && !settingsDocState.doc) {
settingsDocDispatch({
action: DocActions.update,
data: { createdAt: new Date() },
});
}
}, [settingsDocState]);
if (settingsDocState.error?.code === "permission-denied") {
return (
<EmptyState
fullScreen
Icon={SecurityIcon}
message="Access Denied"
description={
<>
<Typography>
You do not have access to this project. Please contact the project
owner.
</Typography>
<Typography>
If you are the project owner, please follow{" "}
<MuiLink
href={WIKI_LINKS.securityRules}
target="_blank"
rel="noopener noreferrer"
>
these instructions
</MuiLink>{" "}
to set up the projects security rules.
</Typography>
const [settingsDocState] = useDoc(
{ path: SETTINGS },
{ createIfMissing: true }
);
<Button component={Link} to={routes.signOut}>
Sign Out
</Button>
</>
}
sx={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
bgcolor: "background.default",
zIndex: 9999,
}}
/>
);
}
if (!Array.isArray(tables))
return view === "list" ? <TableListSkeleton /> : <TableGridSkeleton />;
const TableCard = ({ table }) => {
const checked = Boolean(_find(favs, table));
return (
<Grid key={table.name} item xs={12} sm={6} md={4} lg={4} xl={3}>
<StyledCard
className={classes.card}
overline={table.section}
title={table.name}
headerAction={
<Checkbox
onClick={() => {
userDoc.dispatch({
action: DocActions.update,
data: {
favoriteTables: checked
? favs.filter((t) => t.collection !== table.collection)
: [...favs, table],
},
});
}}
checked={checked}
icon={<FavoriteBorder />}
checkedIcon={<Favorite />}
name="checkedH"
className={classes.favButton}
/>
}
bodyContent={table.description}
primaryLink={{
to: `${
table.isCollectionGroup ? routes.tableGroup : routes.table
}/${table.collection.replace(/\//g, "~2F")}`,
label: "Open",
if (settingsDocState.error?.code === "permission-denied")
return <AccessDenied />;
const createTableFab = (
<Tooltip title="Create Table">
<Zoom in>
<Fab
color="secondary"
aria-label="Create table"
onClick={handleCreateTable}
sx={{
zIndex: "speedDial",
position: "fixed",
bottom: (theme) => ({ xs: theme.spacing(2), sm: theme.spacing(3) }),
right: (theme) => ({ xs: theme.spacing(2), sm: theme.spacing(3) }),
}}
secondaryAction={
<IconButton
onClick={() =>
setSettingsDialogState({
mode: TableSettingsDialogModes.update,
data: table,
})
}
aria-label="Edit table"
>
<EditIcon />
</IconButton>
}
/>
</Grid>
>
<AddIcon />
</Fab>
</Zoom>
</Tooltip>
);
if (tables.length === 0 && userClaims.roles.includes("ADMIN"))
return (
<>
<HomeWelcomePrompt />
{createTableFab}
</>
);
const getLink = (table: Table) =>
`${
table.isCollectionGroup ? routes.tableGroup : routes.table
}/${table.id.replace(/\//g, "~2F")}`;
const handleFavorite = (id: string) => (e: ChangeEvent<HTMLInputElement>) => {
const newFavorites = e.target.checked
? [...favorites, id]
: favorites.filter((f) => f !== id);
userDoc.dispatch({
action: DocActions.update,
data: { favoriteTables: newFavorites },
});
};
const getActions = (table: Table) => (
<>
{userClaims.roles.includes("ADMIN") && (
<IconButton
aria-label="Edit table"
onClick={() =>
setSettingsDialogState({
mode: TableSettingsDialogModes.update,
data: table as any,
})
}
size={view === "list" ? "large" : undefined}
>
<EditIcon />
</IconButton>
)}
<Checkbox
onChange={handleFavorite(table.id)}
checked={favorites.includes(table.id)}
icon={<FavoriteBorderIcon />}
checkedIcon={
<Zoom in>
<FavoriteIcon />
</Zoom>
}
name={`favorite-${table.id}`}
inputProps={{ "aria-label": "Favorite" }}
sx={view === "list" ? { p: 1.5 } : undefined}
color="secondary"
/>
</>
);
return (
<main className={classes.root}>
{sections && Object.keys(sections).length > 0 ? (
<Container>
{favs.length !== 0 && (
<section id="favorites" className={classes.section}>
<Typography
variant="h6"
component="h1"
className={classes.sectionHeader}
>
Favorites
</Typography>
<Divider className={classes.divider} />
<Grid
container
spacing={4}
justifyContent="flex-start"
className={classes.cardGrid}
>
{favs.map((table) => (
<TableCard key={table.collection} table={table} />
))}
</Grid>
</section>
)}
<Container component="main" sx={{ px: 1, pt: 1, pb: 7 + 3 + 3 }}>
<FloatingSearch
label="Search Tables"
onChange={(e) => handleQuery(e.target.value)}
paperSx={{
maxWidth: (theme) => theme.breakpoints.values.sm - 48,
width: { xs: "100%", md: "50%", lg: "100%" },
mx: "auto",
mb: { xs: 2, md: -6 },
}}
/>
{sections &&
Object.keys(sections).length > 0 &&
Object.keys(sections).map((sectionName) => (
<section
key={sectionName}
id={sectionName}
className={classes.section}
>
<Typography
variant="h6"
component="h1"
className={classes.sectionHeader}
>
{sectionName === "undefined" ? "Other" : sectionName}
</Typography>
<Stack
direction="row"
spacing={2}
alignItems="center"
justifyContent="space-between"
>
<Typography
variant="h6"
component="h1"
sx={{ pl: 2, cursor: "default" }}
>
{query ? `${results.length} of ${tables.length}` : tables.length}{" "}
Tables
</Typography>
<Divider className={classes.divider} />
<ToggleButtonGroup
value={view}
exclusive
onChange={(_, v) => {
if (v !== null) setView(v);
}}
aria-label="Table view"
sx={{ "& .MuiToggleButton-root": { borderRadius: 2 } }}
>
<ToggleButton value="list" aria-label="List view">
<ViewListIcon style={{ transform: "rotate(180deg)" }} />
</ToggleButton>
<ToggleButton value="grid" aria-label="Grid view">
<ViewGridIcon />
</ToggleButton>
</ToggleButtonGroup>
</Stack>
<Grid
container
spacing={4}
justifyContent="flex-start"
className={classes.cardGrid}
>
{sections[sectionName].map((table, i) => (
<TableCard key={`${i}-${table.collection}`} table={table} />
))}
</Grid>
</section>
))}
<section className={classes.section}>
<Tooltip title="Create Table">
<Fab
className={classes.fab}
color="secondary"
aria-label="Create table"
onClick={handleCreateTable}
>
<AddIcon />
</Fab>
</Tooltip>
</section>
</Container>
{view === "list" ? (
<TableList
sections={sections}
getLink={getLink}
getActions={getActions}
/>
) : (
<Container>
<HomeWelcomePrompt />
<Fab
className={classes.fab}
color="secondary"
aria-label="Create table"
onClick={handleCreateTable}
>
<AddIcon />
</Fab>
</Container>
<TableGrid
sections={sections}
getLink={getLink}
getActions={getActions}
/>
)}
<TableSettingsDialog
clearDialog={clearDialog}
mode={settingsDialogState.mode}
data={settingsDialogState.data}
/>
</main>
{userClaims.roles.includes("ADMIN") && (
<>
{createTableFab}
<TableSettingsDialog
clearDialog={clearDialog}
mode={settingsDialogState.mode}
data={settingsDialogState.data}
/>
</>
)}
</Container>
);
}

View File

@@ -69,7 +69,7 @@ export default function ProjectSettingsPage() {
);
return (
<Container maxWidth="sm" sx={{ px: 1, pt: 2, pb: 7 }}>
<Container maxWidth="sm" sx={{ px: 1, pt: 1, pb: 7 + 3 + 3 }}>
{settingsState.loading || publicSettingsState.loading ? (
<Stack spacing={4}>
<SettingsSkeleton />
@@ -78,15 +78,15 @@ export default function ProjectSettingsPage() {
</Stack>
) : (
<Stack spacing={4}>
<SettingsSection title="About">
<SettingsSection title="About" transitionTimeout={100}>
<About />
</SettingsSection>
<SettingsSection title={`${name} Run`}>
<SettingsSection title={`${name} Run`} transitionTimeout={200}>
<CloudRun {...childProps} />
</SettingsSection>
<SettingsSection title="Authentication">
<SettingsSection title="Authentication" transitionTimeout={300}>
<Authentication {...childProps} />
</SettingsSection>
</Stack>

View File

@@ -1,12 +1,13 @@
import { useSearch } from "react-use-search";
import { Container, Stack, Typography, Paper, List } from "@material-ui/core";
import FloatingSearch from "components/FloatingSearch";
import SlideTransition from "components/Modal/SlideTransition";
import UserItem from "components/Settings/UserManagement/UserItem";
import UserSkeleton from "components/Settings/UserManagement/UserSkeleton";
import InviteUser from "components/Settings/UserManagement/InviteUser";
import useCollection from "hooks/useCollection";
import useBasicSearch from "hooks/useBasicSearch";
import { USERS } from "config/dbPaths";
export interface User {
@@ -20,53 +21,66 @@ export interface User {
export default function UserManagementPage() {
const [usersState] = useCollection({ path: USERS });
const users: User[] = usersState.documents ?? [];
const loading = usersState.loading || !Array.isArray(usersState.documents);
const [filteredUsers, query, handleChange] = useSearch<User>(
usersState.documents ?? [],
const [results, query, handleQuery] = useBasicSearch(
users,
(user, query) =>
user.id === query ||
user.user.displayName.includes(query) ||
user.user.email.includes(query),
{ filter: true, debounce: 200 }
user.id.toLowerCase() === query ||
user.user.displayName.toLowerCase().includes(query) ||
user.user.email.toLowerCase().includes(query)
);
return (
<Container maxWidth="sm" sx={{ px: 1, pt: 2, pb: 7 }}>
<FloatingSearch label="Search Users" onChange={handleChange as any} />
<Container maxWidth="sm" sx={{ px: 1, pt: 1, pb: 7 + 3 + 3 }}>
<FloatingSearch
label="Search Users"
onChange={(e) => handleQuery(e.target.value)}
/>
<Stack
direction="row"
spacing={2}
justifyContent="space-between"
alignItems="baseline"
sx={{ mt: 4, mx: 1, mb: 0.5, cursor: "default" }}
>
<Typography variant="subtitle1" component="h2">
Users
</Typography>
{!loading && (
<Typography variant="button" component="div">
{query
? `${filteredUsers.length} of ${usersState.documents.length}`
: usersState.documents.length}
<SlideTransition in timeout={100}>
<Stack
direction="row"
spacing={2}
justifyContent="space-between"
alignItems="baseline"
sx={{ mt: 4, mx: 1, mb: 0.5, cursor: "default" }}
>
<Typography variant="subtitle1" component="h2">
Users
</Typography>
)}
</Stack>
<Paper>
<List>
{loading || (query === "" && filteredUsers.length === 0) ? (
<>
<UserSkeleton />
<UserSkeleton />
<UserSkeleton />
</>
) : (
filteredUsers.map((user) => <UserItem key={user.id} {...user} />)
{!loading && (
<Typography variant="button" component="div">
{query
? `${results.length} of ${usersState.documents.length}`
: usersState.documents.length}
</Typography>
)}
</List>
</Paper>
</Stack>
</SlideTransition>
{loading || (query === "" && results.length === 0) ? (
<Paper>
<List>
<UserSkeleton />
<UserSkeleton />
<UserSkeleton />
</List>
</Paper>
) : (
<SlideTransition in timeout={100 + 50}>
<Paper>
<List>
{results.map((user) => (
<UserItem key={user.id} {...user} />
))}
</List>
</Paper>
</SlideTransition>
)}
<InviteUser />
</Container>
);
}

View File

@@ -4,7 +4,7 @@ import SettingsSection from "components/Settings/SettingsSection";
export default function UserSettingsPage() {
return (
<Container maxWidth="sm" sx={{ px: 1, pt: 2, pb: 7 }}>
<Container maxWidth="sm" sx={{ px: 1, pt: 1, pb: 7 + 3 + 3 }}>
<Stack spacing={4}>
<SettingsSection title="Your Account">TODO:</SettingsSection>
</Stack>

View File

@@ -5,18 +5,11 @@ import { colord, extend } from "colord";
import lchPlugin from "colord/plugins/lch";
extend([lchPlugin]);
// declare module "@material-ui/core/styles" {
// interface Palette {
// input: string;
// }
// interface PaletteOptions {
// input?: string;
// }
// }
declare module "@material-ui/core/styles/createPalette" {
interface TypeAction {
activeOpacity: number;
input: string;
inputOutline: string;
}
}
@@ -68,6 +61,7 @@ export const colorsLight = (
disabled: textBase.alpha(0.26).toHslString(),
disabledBackground: textBase.alpha(0.12).toHslString(),
input: "#fff",
inputOutline: shadowBase.alpha(0.12).toRgbString(),
},
divider: shadowBase.alpha(0.12).toRgbString(), // Using hsl string breaks table borders
},
@@ -159,6 +153,7 @@ export const colorsDark = (
hover: "rgba(255, 255, 255, 0.08)",
hoverOpacity: 0.08,
input: "rgba(255, 255, 255, 0.06)",
inputOutline: "rgba(255, 255, 255, 0.08)",
},
// success: { light: "#34c759" },
},

View File

@@ -1,4 +1,5 @@
import { Theme, ThemeOptions } from "@material-ui/core/styles";
import { toRem } from "./typography";
import RadioIcon from "theme/RadioIcon";
import CheckboxIcon from "theme/CheckboxIcon";
@@ -15,10 +16,6 @@ declare module "@material-ui/core/styles/createTransitions" {
}
export const components = (theme: Theme): ThemeOptions => {
const colorDividerHalf = colord(theme.palette.divider)
.alpha(colord(theme.palette.divider).alpha() / 2)
.toHslString();
const buttonPrimaryHover = colord(theme.palette.primary.main)
.mix(theme.palette.primary.contrastText, 0.12)
.alpha(1)
@@ -151,22 +148,29 @@ export const components = (theme: Theme): ThemeOptions => {
backgroundColor: theme.palette.action.input,
},
boxShadow: `0 0 0 1px ${
theme.palette.mode === "dark"
? colorDividerHalf
: theme.palette.divider
} inset`,
borderRadius: theme.shape.borderRadius,
boxShadow: `0 0 0 1px ${theme.palette.action.inputOutline} inset,
0 -1px 0 0 ${theme.palette.text.disabled} inset`,
transition: theme.transitions.create("box-shadow", {
duration: theme.transitions.duration.short,
}),
overflow: "hidden",
"&::before": {
borderRadius: theme.shape.borderRadius,
height: (theme.shape.borderRadius as number) * 2,
borderColor: theme.palette.text.disabled,
"&:hover": {
boxShadow: `0 0 0 1px ${theme.palette.action.inputOutline} inset,
0 -1px 0 0 ${theme.palette.text.primary} inset`,
},
"&.Mui-focused::before, &.Mui-focused:hover::before": {
borderColor: theme.palette.primary.main,
"&.Mui-focused, &.Mui-focused:hover": {
boxShadow: `0 0 0 1px ${theme.palette.action.inputOutline} inset,
0 -2px 0 0 ${theme.palette.primary.main} inset`,
},
borderRadius: theme.shape.borderRadius,
overflow: "hidden",
"&::before": { content: "none" },
"&::after": {
width: `calc(100% - ${
(theme.shape.borderRadius as number) * 2
}px)`,
left: theme.shape.borderRadius,
},
"&.Mui-disabled": {
@@ -181,9 +185,14 @@ export const components = (theme: Theme): ThemeOptions => {
borderColor: theme.palette.secondary.main,
},
},
input: {
paddingTop: theme.spacing(1.5),
paddingBottom: theme.spacing(13 / 8),
height: toRem(23),
},
inputSizeSmall: {
padding: "6px 12px",
height: 20,
padding: theme.spacing(0.75, 1.5),
height: toRem(20),
},
multiline: { padding: 0 },
},
@@ -272,7 +281,7 @@ export const components = (theme: Theme): ThemeOptions => {
"& .MuiListItemIcon-root": {
minWidth: 24 + 12,
"& svg": { fontSize: "1.5rem" },
"& svg": { fontSize: toRem(24) },
},
"& + .MuiDivider-root": {
@@ -344,12 +353,10 @@ export const components = (theme: Theme): ThemeOptions => {
outlined: {
"&, &:hover, &.Mui-disabled": { border: "none" },
boxShadow:
theme.palette.mode === "dark"
? `0 0 0 1px ${colorDividerHalf} inset,
0 1px 0 0 ${colorDividerHalf} inset`
: `0 0 0 1px ${theme.palette.divider} inset,
0 -1px 0 0 ${theme.palette.divider} inset`,
boxShadow: `0 0 0 1px ${theme.palette.action.inputOutline} inset,
0 ${theme.palette.mode === "dark" ? "" : "-"}1px 0 0 ${
theme.palette.action.inputOutline
} inset`,
backgroundColor: theme.palette.action.input,
"&.Mui-disabled": {
@@ -406,7 +413,10 @@ export const components = (theme: Theme): ThemeOptions => {
TouchRippleProps: { center: false },
},
styleOverrides: {
sizeSmall: { borderRadius: theme.shape.borderRadius },
sizeSmall: {
borderRadius: theme.shape.borderRadius,
padding: theme.spacing(0.5),
},
},
},
MuiFab: {
@@ -596,6 +606,9 @@ export const components = (theme: Theme): ThemeOptions => {
icon: <RadioIcon />,
checkedIcon: <RadioIcon />,
},
styleOverrides: {
root: { padding: theme.spacing(1) },
},
},
MuiCheckbox: {
defaultProps: {
@@ -603,6 +616,9 @@ export const components = (theme: Theme): ThemeOptions => {
checkedIcon: <CheckboxIcon />,
indeterminateIcon: <CheckboxIndeterminateIcon />,
},
styleOverrides: {
root: { padding: theme.spacing(1) },
},
},
MuiSlider: {

View File

@@ -3201,7 +3201,7 @@
hoist-non-react-statics "^3.3.0"
redux "^4.0.0"
"@types/react-router-dom@^5.1.7":
"@types/react-router-dom@*", "@types/react-router-dom@^5.1.7":
version "5.1.8"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.8.tgz#bf3e1c8149b3d62eaa206d58599de82df0241192"
integrity sha512-03xHyncBzG0PmDmf8pf3rehtjY0NpUj7TIN46FrT5n1ZWHPZvXz32gUyNboJ+xsL8cpg8bQVLcllptcQHvocrw==
@@ -3210,6 +3210,14 @@
"@types/react" "*"
"@types/react-router" "*"
"@types/react-router-hash-link@^2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@types/react-router-hash-link/-/react-router-hash-link-2.4.1.tgz#9e5da328817e12f489a2005613857cbf50127e61"
integrity sha512-SPVymyscQUBWMAPEpAn3I35MQXarTx0rOPmcfHl1xWYaTSDP5kxQnrFjmMxJlX5mjIPVHb3XBm8t6DTQNjkGEQ==
dependencies:
"@types/react" "*"
"@types/react-router-dom" "*"
"@types/react-router@*":
version "5.1.16"
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.16.tgz#f3ba045fb96634e38b21531c482f9aeb37608a99"
@@ -13634,6 +13642,13 @@ react-router-dom@^5.0.1:
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react-router-hash-link@^2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/react-router-hash-link/-/react-router-hash-link-2.4.3.tgz#570824d53d6c35ce94d73a46c8e98673a127bf08"
integrity sha512-NU7GWc265m92xh/aYD79Vr1W+zAIXDWp3L2YZOYP4rCqPnJ6LI6vh3+rKgkidtYijozHclaEQTAHaAaMWPVI4A==
dependencies:
prop-types "^15.7.2"
react-router@5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293"
@@ -13740,13 +13755,6 @@ react-transition-group@^4.4.0, react-transition-group@^4.4.1:
loose-envify "^1.4.0"
prop-types "^15.6.2"
react-use-search@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/react-use-search/-/react-use-search-0.3.1.tgz#1a36434a0f0a0fc2600a743a2c170ad88f718787"
integrity sha512-Ec8+5AnOMunnA532ziNLzs3W/4l9MhecvRF1fi2ho9JvXBgT/pmdFpgN3fRfPCHAsMf9LpEo4LxDZDs/BdTfNQ==
dependencies:
lodash.debounce "^4.0.8"
react-usestateref@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/react-usestateref/-/react-usestateref-1.0.5.tgz#0b5659217022a7e88087875ec1745730940b7882"