mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: Add sidebar and panel (#866)
* feat: Add sidebar and panel * fix: fix sidebar values
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import { Story } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { Panel, PanelProps } from "./"
|
||||
|
||||
export default {
|
||||
title: "Page/Panel",
|
||||
component: Panel,
|
||||
}
|
||||
|
||||
const Template: Story<PanelProps> = (args: PanelProps) => <Panel {...args} />
|
||||
|
||||
export const Example = Template.bind({})
|
||||
Example.args = {
|
||||
title: "Panel title",
|
||||
activeTab: "oauthSettings",
|
||||
menuItems: [
|
||||
{ label: "OAuth Settings", value: "oauthSettings" },
|
||||
{ label: "Security", value: "oauthSettings", hasChanges: true },
|
||||
{ label: "Hardware", value: "oauthSettings" },
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { fade } from "@material-ui/core/styles/colorManipulator"
|
||||
import React from "react"
|
||||
import { Sidebar, SidebarItem } from "../Sidebar"
|
||||
|
||||
export type AdminMenuItemCallback = (menuItem: string) => void
|
||||
|
||||
export interface PanelProps {
|
||||
title: string
|
||||
menuItems: SidebarItem[]
|
||||
activeTab: string
|
||||
onSelect: AdminMenuItemCallback
|
||||
}
|
||||
|
||||
export const Panel: React.FC<PanelProps> = ({ children, title, menuItems, activeTab, onSelect }) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div className={styles.inner}>
|
||||
<div className={styles.menuPanel}>
|
||||
<div className={styles.title}>{title}</div>
|
||||
<Sidebar menuItems={menuItems} activeItem={activeTab} onSelect={onSelect} />
|
||||
</div>
|
||||
|
||||
<div className={styles.contentPanel}>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
minHeight: 400,
|
||||
marginBottom: theme.spacing(2),
|
||||
// Prevents scrollbar jitter from long content
|
||||
marginRight: "calc(-1 * (100vw - 100%))",
|
||||
},
|
||||
|
||||
inner: {
|
||||
display: "flex",
|
||||
maxWidth: 1920,
|
||||
padding: theme.spacing(5, 3.5, 0, 4),
|
||||
},
|
||||
|
||||
icon: {
|
||||
fontSize: 100,
|
||||
position: "absolute",
|
||||
left: -50,
|
||||
top: 31,
|
||||
color: fade(theme.palette.common.black, 0.1),
|
||||
transition: "transform 0.3s ease",
|
||||
zIndex: -1,
|
||||
},
|
||||
|
||||
menuPanel: {
|
||||
paddingRight: 40,
|
||||
},
|
||||
|
||||
title: {
|
||||
marginTop: theme.spacing(4),
|
||||
fontSize: 32,
|
||||
letterSpacing: -theme.spacing(0.0375),
|
||||
},
|
||||
|
||||
contentPanel: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
maxWidth: 930,
|
||||
},
|
||||
|
||||
[theme.breakpoints.up("lg")]: {
|
||||
icon: {
|
||||
position: "relative",
|
||||
top: -11,
|
||||
left: 30,
|
||||
},
|
||||
contentPanel: {
|
||||
width: 930,
|
||||
},
|
||||
},
|
||||
|
||||
[theme.breakpoints.down("lg")]: {
|
||||
contentPanel: {
|
||||
width: 890,
|
||||
},
|
||||
},
|
||||
[theme.breakpoints.down("md")]: {
|
||||
contentPanel: {
|
||||
width: 700,
|
||||
},
|
||||
},
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
contentPanel: {
|
||||
width: 550,
|
||||
},
|
||||
root: {
|
||||
marginRight: 0, //disabled scrollbar jump trick to avoid small screen bug with menu
|
||||
},
|
||||
},
|
||||
}))
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Story } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { Sidebar, SidebarProps } from "./"
|
||||
|
||||
export default {
|
||||
title: "Page/Sidebar",
|
||||
component: Sidebar,
|
||||
}
|
||||
|
||||
const Template: Story<SidebarProps> = (args: SidebarProps) => <Sidebar {...args} />
|
||||
|
||||
export const Example = Template.bind({})
|
||||
Example.args = {
|
||||
activeItem: "oauthSettings",
|
||||
menuItems: [
|
||||
{ label: "OAuth Settings", value: "oauthSettings" },
|
||||
{ label: "Security", value: "security", hasChanges: true },
|
||||
{ label: "Hardware", value: "hardware" },
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import List from "@material-ui/core/List"
|
||||
import ListItem from "@material-ui/core/ListItem"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import React from "react"
|
||||
import { combineClasses } from "../../util/combine-classes"
|
||||
|
||||
export interface SidebarItem {
|
||||
value: string
|
||||
label: string
|
||||
hasChanges?: boolean
|
||||
}
|
||||
|
||||
export interface SidebarProps {
|
||||
menuItems: SidebarItem[]
|
||||
activeItem?: string
|
||||
onSelect?: (value: string) => void
|
||||
}
|
||||
|
||||
export const Sidebar: React.FC<SidebarProps> = ({ menuItems, activeItem, onSelect }) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<List className={styles.menu}>
|
||||
{menuItems.map(({ hasChanges, ...tab }) => {
|
||||
const isActive = activeItem === tab.value
|
||||
return (
|
||||
<ListItem
|
||||
key={tab.value}
|
||||
button
|
||||
onClick={onSelect ? () => onSelect(tab.value) : undefined}
|
||||
className={styles.menuItem}
|
||||
disableRipple
|
||||
focusRipple={false}
|
||||
component="li"
|
||||
>
|
||||
<span className={combineClasses({ [styles.menuItemSpan]: true, active: isActive })}>
|
||||
{hasChanges ? `${tab.label}*` : tab.label}
|
||||
</span>
|
||||
</ListItem>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
menu: {
|
||||
minWidth: 160,
|
||||
marginTop: theme.spacing(5),
|
||||
},
|
||||
|
||||
menuItem: {
|
||||
letterSpacing: -theme.spacing(0.0375),
|
||||
padding: 0,
|
||||
fontSize: 18,
|
||||
color: theme.palette.text.secondary,
|
||||
|
||||
"&.MuiListItem-button:hover": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
},
|
||||
|
||||
menuItemSpan: {
|
||||
paddingTop: theme.spacing(1.5),
|
||||
paddingBottom: theme.spacing(1),
|
||||
transition: "300ms all ease",
|
||||
position: "relative",
|
||||
|
||||
"&:hover": {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
"&.active": {
|
||||
color: theme.palette.primary.dark,
|
||||
},
|
||||
|
||||
"&.active, &:hover": {
|
||||
"&::before": {
|
||||
opacity: 1,
|
||||
},
|
||||
"&::after": {
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
[theme.breakpoints.up("lg")]: {
|
||||
menuItemSpan: {
|
||||
"&::before": {
|
||||
content: "'{'",
|
||||
position: "absolute",
|
||||
left: -15,
|
||||
opacity: 0,
|
||||
transition: "inherit",
|
||||
},
|
||||
"&::after": {
|
||||
content: "'}'",
|
||||
position: "absolute",
|
||||
right: -15,
|
||||
opacity: 0,
|
||||
transition: "inherit",
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
@@ -0,0 +1,562 @@
|
||||
import { darken, fade } from "@material-ui/core/styles/colorManipulator"
|
||||
import { Breakpoints } from "@material-ui/core/styles/createBreakpoints"
|
||||
import {
|
||||
BODY_FONT_FAMILY,
|
||||
borderRadius,
|
||||
buttonBorderWidth,
|
||||
emptyBoxShadow,
|
||||
lightButtonShadow,
|
||||
spacing,
|
||||
} from "./constants"
|
||||
import { CustomPalette } from "./palettes"
|
||||
import { typography } from "./typography"
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const getOverrides = (palette: CustomPalette, breakpoints: Breakpoints) => {
|
||||
const containedButtonShadow = palette.type === "dark" ? emptyBoxShadow : lightButtonShadow
|
||||
return {
|
||||
MuiAvatar: {
|
||||
root: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
fontSize: 24,
|
||||
},
|
||||
},
|
||||
MuiTable: {
|
||||
root: {
|
||||
backgroundColor: palette.background.paper,
|
||||
borderRadius: borderRadius * 2,
|
||||
boxShadow: `0 0 0 1px ${palette.action.hover}`,
|
||||
|
||||
"&.is-inline": {
|
||||
background: "none",
|
||||
borderRadius: 0,
|
||||
boxShadow: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTableHead: {
|
||||
root: {
|
||||
"& .MuiTableCell-root": {
|
||||
...typography.overline,
|
||||
paddingTop: spacing,
|
||||
paddingBottom: spacing,
|
||||
color: palette.text.secondary,
|
||||
backgroundColor: palette.background.default,
|
||||
|
||||
".MuiTable-root.is-inline &": {
|
||||
background: "none",
|
||||
},
|
||||
},
|
||||
"& .MuiTableRow-root:first-child .MuiTableCell-root:first-child": {
|
||||
borderTopLeftRadius: borderRadius * 2,
|
||||
|
||||
".MuiTable-root.is-inline &": {
|
||||
borderRadius: 0,
|
||||
},
|
||||
},
|
||||
"& .MuiTableRow-root:first-child .MuiTableCell-root:last-child": {
|
||||
borderTopRightRadius: borderRadius * 2,
|
||||
|
||||
".MuiTable-root.is-inline &": {
|
||||
borderRadius: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTableSortLabel: {
|
||||
root: {
|
||||
lineHeight: "20px",
|
||||
|
||||
"&$active": {
|
||||
"&& $icon": {
|
||||
color: palette.text.primary,
|
||||
},
|
||||
},
|
||||
},
|
||||
icon: {
|
||||
"&.MuiSvgIcon-root": {
|
||||
width: 18,
|
||||
height: 18,
|
||||
marginLeft: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTableCell: {
|
||||
root: {
|
||||
"&:first-child": {
|
||||
paddingLeft: spacing * 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTablePagination: {
|
||||
input: {
|
||||
height: "auto",
|
||||
marginTop: 0,
|
||||
border: `1px solid ${palette.action.disabled}`,
|
||||
},
|
||||
|
||||
select: {
|
||||
paddingTop: 6,
|
||||
fontSize: 14,
|
||||
},
|
||||
|
||||
selectIcon: {
|
||||
right: -4,
|
||||
},
|
||||
},
|
||||
MuiList: {
|
||||
root: {
|
||||
boxSizing: "border-box",
|
||||
|
||||
"& .MuiDivider-root": {
|
||||
opacity: 0.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiListItem: {
|
||||
root: {
|
||||
minHeight: 24,
|
||||
position: "relative",
|
||||
"&.active": {
|
||||
color: "white",
|
||||
},
|
||||
},
|
||||
gutters: {
|
||||
padding: spacing / 2,
|
||||
paddingLeft: spacing * 2,
|
||||
paddingRight: spacing * 2,
|
||||
},
|
||||
},
|
||||
MuiListItemAvatar: {
|
||||
root: {
|
||||
minWidth: 46,
|
||||
},
|
||||
},
|
||||
MuiListItemIcon: {
|
||||
root: {
|
||||
minWidth: 36,
|
||||
color: palette.text.primary,
|
||||
|
||||
"& .MuiSvgIcon-root": {
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiListSubheader: {
|
||||
root: {
|
||||
...typography.overline,
|
||||
lineHeight: 2.5,
|
||||
},
|
||||
sticky: {
|
||||
backgroundColor: palette.background.paper,
|
||||
},
|
||||
},
|
||||
MuiMenu: {
|
||||
paper: {
|
||||
marginTop: spacing,
|
||||
},
|
||||
},
|
||||
MuiMenuItem: {
|
||||
root: {
|
||||
minHeight: 48,
|
||||
fontSize: 16,
|
||||
[breakpoints.up("sm")]: {
|
||||
minHeight: 48,
|
||||
},
|
||||
},
|
||||
gutters: {
|
||||
padding: spacing,
|
||||
paddingLeft: spacing * 2,
|
||||
paddingRight: spacing * 2,
|
||||
},
|
||||
},
|
||||
MuiListItemText: {
|
||||
primary: {
|
||||
fontWeight: 500,
|
||||
},
|
||||
secondary: {
|
||||
fontSize: 12,
|
||||
},
|
||||
},
|
||||
MuiOutlinedInput: {
|
||||
root: {
|
||||
fontSize: 14,
|
||||
"&$focused $notchedOutline": {
|
||||
borderWidth: 1,
|
||||
},
|
||||
},
|
||||
notchedOutline: {
|
||||
borderWidth: 1,
|
||||
borderColor: palette.action.disabled,
|
||||
},
|
||||
},
|
||||
MuiButton: {
|
||||
root: {
|
||||
minHeight: 40,
|
||||
paddingLeft: 20,
|
||||
paddingRight: 20,
|
||||
fontWeight: 500,
|
||||
|
||||
"& .MuiSvgIcon-root": {
|
||||
verticalAlign: "middle",
|
||||
},
|
||||
},
|
||||
|
||||
contained: {
|
||||
backgroundColor: palette.hero.button,
|
||||
color: palette.primary.contrastText,
|
||||
boxShadow: containedButtonShadow,
|
||||
"&:hover": {
|
||||
backgroundColor: darken(palette.hero.button, 0.25),
|
||||
},
|
||||
"&$disabled": {
|
||||
color: fade(palette.text.disabled, 0.5),
|
||||
},
|
||||
},
|
||||
containedPrimary: {
|
||||
boxShadow: containedButtonShadow,
|
||||
},
|
||||
containedSecondary: {
|
||||
boxShadow: containedButtonShadow,
|
||||
},
|
||||
|
||||
outlined: {
|
||||
borderColor: palette.action.disabled,
|
||||
borderWidth: buttonBorderWidth,
|
||||
"&:hover": {
|
||||
color: palette.primary.main,
|
||||
borderColor: palette.primary.main,
|
||||
borderWidth: buttonBorderWidth,
|
||||
},
|
||||
"&$disabled": {
|
||||
color: fade(palette.text.disabled, 0.5),
|
||||
},
|
||||
},
|
||||
outlinedPrimary: {
|
||||
borderColor: palette.primary.main,
|
||||
borderWidth: buttonBorderWidth,
|
||||
"&:hover": {
|
||||
borderWidth: buttonBorderWidth,
|
||||
},
|
||||
},
|
||||
outlinedSecondary: {
|
||||
borderColor: palette.secondary.main,
|
||||
borderWidth: buttonBorderWidth,
|
||||
"&:hover": {
|
||||
color: palette.secondary.main,
|
||||
borderWidth: buttonBorderWidth,
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
"&$disabled": {
|
||||
color: fade(palette.text.disabled, 0.5),
|
||||
},
|
||||
},
|
||||
|
||||
sizeSmall: {
|
||||
minHeight: 32,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
letterSpacing: 1.2,
|
||||
fontSize: 13,
|
||||
|
||||
"&.MuiButton-outlined": {
|
||||
borderWidth: 1,
|
||||
},
|
||||
},
|
||||
sizeLarge: {
|
||||
minHeight: 46,
|
||||
paddingLeft: spacing * 3,
|
||||
paddingRight: spacing * 3,
|
||||
},
|
||||
},
|
||||
MuiButtonGroup: {
|
||||
contained: {
|
||||
boxShadow: containedButtonShadow,
|
||||
},
|
||||
},
|
||||
MuiLink: {
|
||||
root: {
|
||||
fontWeight: 600,
|
||||
},
|
||||
},
|
||||
MuiInputBase: {
|
||||
root: {
|
||||
minHeight: 40,
|
||||
background: palette.background.paper,
|
||||
marginTop: "12px",
|
||||
borderRadius,
|
||||
|
||||
"&$disabled": {
|
||||
background: palette.action.disabledBackground,
|
||||
},
|
||||
|
||||
"&$focused .MuiSelect-icon": {
|
||||
color: palette.primary.light,
|
||||
},
|
||||
},
|
||||
|
||||
input: {
|
||||
fontSize: 16,
|
||||
|
||||
"&::placeholder": {
|
||||
color: palette.text.secondary,
|
||||
opacity: 1,
|
||||
},
|
||||
|
||||
// See this thread https://stackoverflow.com/questions/69196525/how-to-change-the-color-of-the-calendar-icon-of-the-textfield-date-picker-in-ma
|
||||
"&::-webkit-calendar-picker-indicator": {
|
||||
filter:
|
||||
palette.type === "dark"
|
||||
? "invert(95%) sepia(0%) saturate(1082%) hue-rotate(173deg) brightness(84%) contrast(80%);"
|
||||
: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiInputLabel: {
|
||||
shrink: {
|
||||
fontSize: 14,
|
||||
marginTop: "2px",
|
||||
left: "-12px",
|
||||
transform: "translate(16px, -6px) scale(0.8)",
|
||||
color: palette.text.primary,
|
||||
},
|
||||
outlined: {
|
||||
letterSpacing: "0.2px",
|
||||
lineHeight: "16px",
|
||||
fontFamily: BODY_FONT_FAMILY,
|
||||
|
||||
"&$shrink": {
|
||||
transform: "translate(14px, -13px)",
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiChip: {
|
||||
root: {
|
||||
fontSize: 16,
|
||||
fontWeight: 500,
|
||||
borderRadius: borderRadius,
|
||||
backgroundColor: fade(palette.text.secondary, 0.1),
|
||||
color: palette.text.secondary,
|
||||
},
|
||||
sizeSmall: {
|
||||
height: 20,
|
||||
},
|
||||
labelSmall: {
|
||||
fontSize: 12,
|
||||
paddingLeft: 6,
|
||||
paddingRight: 6,
|
||||
},
|
||||
colorPrimary: {
|
||||
backgroundColor: fade(palette.primary.main, 0.1),
|
||||
color: palette.primary.main,
|
||||
},
|
||||
colorSecondary: {
|
||||
backgroundColor: fade(palette.secondary.main, 0.15),
|
||||
color: palette.secondary.main,
|
||||
},
|
||||
},
|
||||
MuiSelect: {
|
||||
root: {
|
||||
// Matches MuiInputBase-input height
|
||||
lineHeight: "1.1875em",
|
||||
overflow: "hidden",
|
||||
},
|
||||
select: {
|
||||
"&:focus": {
|
||||
borderRadius,
|
||||
},
|
||||
},
|
||||
icon: {
|
||||
"&.MuiSvgIcon-root": {
|
||||
fontSize: 20,
|
||||
margin: "3px 6px 0 0",
|
||||
},
|
||||
},
|
||||
iconOutlined: {
|
||||
right: 0,
|
||||
},
|
||||
iconOpen: {
|
||||
transform: "none",
|
||||
},
|
||||
},
|
||||
|
||||
MuiDialog: {
|
||||
paper: {
|
||||
borderRadius: 11,
|
||||
},
|
||||
paperWidthSm: {
|
||||
maxWidth: "530px",
|
||||
},
|
||||
},
|
||||
MuiDialogTitle: {
|
||||
root: {
|
||||
color: palette.text.primary,
|
||||
padding: `27px ${spacing * 6}px 0 ${spacing * 6}px`,
|
||||
fontSize: 32,
|
||||
fontWeight: 700,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
background: palette.background.paper,
|
||||
},
|
||||
},
|
||||
MuiDialogContent: {
|
||||
root: {
|
||||
padding: `0 ${spacing * 6}px ${spacing * 6}px ${spacing * 6}px`,
|
||||
},
|
||||
},
|
||||
MuiDialogContentText: {
|
||||
root: {
|
||||
color: palette.action.active,
|
||||
},
|
||||
},
|
||||
MuiDialogActions: {
|
||||
root: {
|
||||
padding: 0,
|
||||
},
|
||||
spacing: {
|
||||
"& > :not(:first-child)": {
|
||||
marginLeft: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiFormHelperText: {
|
||||
root: {
|
||||
fontSize: 12,
|
||||
lineHeight: "16px",
|
||||
},
|
||||
contained: {
|
||||
marginTop: 6,
|
||||
marginLeft: spacing * 0.5,
|
||||
},
|
||||
},
|
||||
MuiBackdrop: {
|
||||
root: {
|
||||
backgroundColor: fade("#242424", 0.6),
|
||||
},
|
||||
},
|
||||
MuiInputAdornment: {
|
||||
root: {
|
||||
"& .MuiSvgIcon-root": {
|
||||
color: palette.text.secondary,
|
||||
},
|
||||
},
|
||||
positionStart: {
|
||||
marginRight: 12,
|
||||
marginLeft: 6,
|
||||
},
|
||||
},
|
||||
MuiCheckbox: {
|
||||
root: {
|
||||
"& .MuiSvgIcon-root": {
|
||||
width: 16,
|
||||
height: 16,
|
||||
margin: 1,
|
||||
},
|
||||
},
|
||||
colorPrimary: {
|
||||
"&.MuiCheckbox-root": {
|
||||
color: palette.primary.main,
|
||||
"&$disabled": {
|
||||
color: palette.action.disabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
colorSecondary: {
|
||||
"&.MuiCheckbox-root": {
|
||||
color: palette.secondary.main,
|
||||
"&$disabled": {
|
||||
color: palette.action.disabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiRadio: {
|
||||
root: {
|
||||
"& .MuiSvgIcon-root": {
|
||||
width: 18,
|
||||
height: 18,
|
||||
},
|
||||
},
|
||||
colorPrimary: {
|
||||
"&.MuiRadio-root": {
|
||||
color: palette.primary.main,
|
||||
"&$disabled": {
|
||||
color: palette.action.disabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
colorSecondary: {
|
||||
"&.MuiRadio-root": {
|
||||
color: palette.secondary.main,
|
||||
"&$disabled": {
|
||||
color: palette.action.disabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTabs: {
|
||||
root: {
|
||||
minHeight: 40,
|
||||
},
|
||||
indicator: {
|
||||
height: 3,
|
||||
},
|
||||
},
|
||||
MuiTab: {
|
||||
root: {
|
||||
minHeight: 70,
|
||||
marginRight: spacing * 5,
|
||||
textTransform: "none",
|
||||
fontWeight: 400,
|
||||
fontSize: 16,
|
||||
letterSpacing: 3,
|
||||
|
||||
"&:last-child": {
|
||||
marginRight: 0,
|
||||
},
|
||||
|
||||
"&.MuiButtonBase-root": {
|
||||
minWidth: 0,
|
||||
padding: 0,
|
||||
},
|
||||
|
||||
"&.Mui-selected .MuiTab-wrapper": {
|
||||
color: palette.primary.contrastText,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
wrapper: {},
|
||||
textColorPrimary: {
|
||||
color: palette.text.secondary,
|
||||
},
|
||||
textColorSecondary: {
|
||||
color: palette.text.secondary,
|
||||
},
|
||||
},
|
||||
MuiStepper: {
|
||||
root: {
|
||||
background: "none",
|
||||
},
|
||||
},
|
||||
// Labs components aren't typed properly, so cast overrides
|
||||
MuiAutocomplete: {
|
||||
endAdornment: {
|
||||
"& .MuiSvgIcon-root": {
|
||||
fontSize: 20,
|
||||
},
|
||||
},
|
||||
inputRoot: {
|
||||
'&[class*="MuiOutlinedInput-root"]': {
|
||||
"& $endAdornment": {
|
||||
right: 6,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { ComponentsProps } from "@material-ui/core/styles/props"
|
||||
|
||||
/**
|
||||
* These are global overrides to MUI components and we may move away from using them in the future.
|
||||
*/
|
||||
export const props = {
|
||||
MuiButton: {
|
||||
variant: "contained",
|
||||
},
|
||||
MuiTextField: {
|
||||
margin: "dense",
|
||||
InputProps: {
|
||||
labelWidth: 0,
|
||||
},
|
||||
variant: "outlined",
|
||||
spellCheck: false,
|
||||
},
|
||||
MuiInputLabel: {
|
||||
shrink: true,
|
||||
},
|
||||
MuiFormControl: {
|
||||
variant: "outlined",
|
||||
margin: "dense",
|
||||
},
|
||||
MuiInput: {
|
||||
spellCheck: false,
|
||||
autoCorrect: "off",
|
||||
},
|
||||
MuiOutlinedInput: {
|
||||
notched: false,
|
||||
},
|
||||
MuiDialogTitle: {
|
||||
disableTypography: true,
|
||||
},
|
||||
MuiMenu: {
|
||||
anchorOrigin: {
|
||||
vertical: "bottom",
|
||||
horizontal: "right",
|
||||
},
|
||||
transformOrigin: {
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
},
|
||||
// Disable the behavior of placing the select at the selected option
|
||||
getContentAnchorEl: null,
|
||||
},
|
||||
MuiList: {
|
||||
disablePadding: true,
|
||||
},
|
||||
MuiTabs: {
|
||||
textColor: "primary",
|
||||
indicatorColor: "primary",
|
||||
},
|
||||
MuiTab: {
|
||||
disableTouchRipple: true,
|
||||
},
|
||||
} as ComponentsProps
|
||||
@@ -1,15 +1,23 @@
|
||||
import { createMuiTheme } from "@material-ui/core/styles"
|
||||
import { Overrides } from "@material-ui/core/styles/overrides"
|
||||
import { borderRadius } from "./constants"
|
||||
import { getOverrides } from "./overrides"
|
||||
import { CustomPalette, darkPalette, lightPalette } from "./palettes"
|
||||
import { props } from "./props"
|
||||
import { typography } from "./typography"
|
||||
|
||||
const makeTheme = (palette: CustomPalette) => {
|
||||
// Grab defaults to re-use in overrides
|
||||
const { breakpoints } = createMuiTheme()
|
||||
|
||||
return createMuiTheme({
|
||||
palette,
|
||||
typography,
|
||||
shape: {
|
||||
borderRadius,
|
||||
},
|
||||
props,
|
||||
overrides: getOverrides(palette, breakpoints) as Overrides,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
export const appendCSSString = (cssString: string, cssClass: string): string => {
|
||||
if (cssString === "") {
|
||||
return cssClass
|
||||
}
|
||||
return `${cssString} ${cssClass}`
|
||||
}
|
||||
|
||||
/**
|
||||
* @param classes May be an object or an array. When using an object, each key
|
||||
* is a css class name and its associated value indicates whether it should be
|
||||
* applied. When using an array, only truthy values are applied.
|
||||
* @returns A string with each class that should be included. Classes are
|
||||
* separated by two spaces. If no classes are included, returns `undefined`.
|
||||
* @example
|
||||
* combineClasses() // -> undefined
|
||||
* combineClasses({ text: true }) // -> "text"
|
||||
* combineClasses({ text: true, success: true }) // -> "text success"
|
||||
* combineClasses({ text: true, success: false }) // -> "text"
|
||||
* combineClasses([ "text", false, "" ]) // -> "text"
|
||||
*/
|
||||
export const combineClasses = (
|
||||
classes: Array<string | false | undefined> | Record<string, boolean | undefined> = {},
|
||||
): string | undefined => {
|
||||
let result = ""
|
||||
|
||||
if (Array.isArray(classes)) {
|
||||
for (const cssClass of classes) {
|
||||
if (cssClass) {
|
||||
result = appendCSSString(result, cssClass)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const cssClass in classes) {
|
||||
const useClass = classes[cssClass]
|
||||
if (useClass) {
|
||||
result = appendCSSString(result, cssClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.length ? result : undefined
|
||||
}
|
||||
Reference in New Issue
Block a user