core-components: rewrite component components to use functions

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2021-09-12 13:40:50 +02:00
parent 2485ea499d
commit eb4fecca82
39 changed files with 256 additions and 935 deletions
File diff suppressed because it is too large Load Diff
@@ -22,7 +22,7 @@ import { AlertMessage, useApi, alertApiRef } from '@backstage/core-plugin-api';
import pluralize from 'pluralize';
// TODO: improve on this and promote to a shared component for use by all apps.
export const AlertDisplay = () => {
export function AlertDisplay(_props: {}) {
const [messages, setMessages] = useState<Array<AlertMessage>>([]);
const alertApi = useApi(alertApiRef);
@@ -73,4 +73,4 @@ export const AlertDisplay = () => {
</Alert>
</Snackbar>
);
};
}
@@ -41,7 +41,8 @@ export type AvatarProps = {
customStyles?: CSSProperties;
};
export const Avatar = ({ displayName, picture, customStyles }: AvatarProps) => {
export function Avatar(props: AvatarProps) {
const { displayName, picture, customStyles } = props;
const classes = useStyles();
return (
<MaterialAvatar
@@ -56,4 +57,4 @@ export const Avatar = ({ displayName, picture, customStyles }: AvatarProps) => {
{displayName && extractInitials(displayName)}
</MaterialAvatar>
);
};
}
@@ -23,10 +23,19 @@ import { Link, LinkProps } from '../Link';
type Props = MaterialButtonProps & Omit<LinkProps, 'variant' | 'color'>;
declare function ButtonType(props: Props): JSX.Element;
/**
* Thin wrapper on top of material-ui's Button component
* Makes the Button to utilise react-router
*/
export const Button = React.forwardRef<any, Props>((props, ref) => (
const ActualButton = React.forwardRef<any, Props>((props, ref) => (
<MaterialButton ref={ref} component={Link} {...props} />
));
)) as { (props: Props): JSX.Element };
// TODO(Rugvip): We use this as a workaround to make the exported type be a
// function, which makes our API reference docs much nicer.
// The first type to be exported gets priority, but it will
// be thrown away when compiling to JS.
// @ts-ignore
export { ButtonType as Button, ActualButton as Button };
@@ -225,13 +225,8 @@ const indexer = (
};
}, {});
export const CheckboxTree = ({
subCategories,
label,
selected,
onChange,
triggerReset,
}: CheckboxTreeProps) => {
export function CheckboxTree(props: CheckboxTreeProps) {
const { subCategories, label, selected, onChange, triggerReset } = props;
const classes = useStyles();
const [state, dispatch] = useReducer(reducer, indexer(subCategories));
@@ -355,4 +350,4 @@ export const CheckboxTree = ({
</List>
</div>
);
};
}
@@ -30,14 +30,15 @@ type Props = {
customStyle?: any;
};
export const CodeSnippet = ({
text,
language,
showLineNumbers = false,
showCopyCodeButton = false,
highlightedNumbers,
customStyle,
}: Props) => {
export const CodeSnippet = (props: Props) => {
const {
text,
language,
showLineNumbers = false,
showCopyCodeButton = false,
highlightedNumbers,
customStyle,
} = props;
const theme = useTheme<BackstageTheme>();
const mode = theme.palette.type === 'dark' ? dark : docco;
const highlightColor = theme.palette.type === 'dark' ? '#256bf3' : '#e6ffed';
@@ -47,7 +47,7 @@ const defaultProps = {
tooltipText: 'Text copied to clipboard',
};
export const CopyTextButton = (props: Props) => {
export function CopyTextButton(props: Props) {
const { text, tooltipDelay, tooltipText } = {
...defaultProps,
...props,
@@ -84,7 +84,7 @@ export const CopyTextButton = (props: Props) => {
</Tooltip>
</>
);
};
}
// Type check for the JS files using this core component
CopyTextButton.propTypes = {
@@ -24,7 +24,8 @@ type CreateButtonProps = {
title: string;
} & Partial<Pick<LinkProps, 'to'>>;
export const CreateButton = ({ title, to }: CreateButtonProps) => {
export function CreateButton(props: CreateButtonProps) {
const { title, to } = props;
const isXSScreen = useMediaQuery<BackstageTheme>(theme =>
theme.breakpoints.down('xs'),
);
@@ -48,4 +49,4 @@ export const CreateButton = ({ title, to }: CreateButtonProps) => {
{title}
</Button>
);
};
}
@@ -60,27 +60,28 @@ export type DependencyGraphProps = React.SVGProps<SVGSVGElement> & {
const WORKSPACE_ID = 'workspace';
export function DependencyGraph({
edges,
nodes,
renderNode,
direction = Direction.TOP_BOTTOM,
align,
nodeMargin = 50,
edgeMargin = 10,
rankMargin = 50,
paddingX = 0,
paddingY = 0,
acyclicer,
ranker = Ranker.NETWORK_SIMPLEX,
labelPosition = LabelPosition.RIGHT,
labelOffset = 10,
edgeRanks = 1,
edgeWeight = 1,
renderLabel,
defs,
...svgProps
}: DependencyGraphProps) {
export function DependencyGraph(props: DependencyGraphProps) {
const {
edges,
nodes,
renderNode,
direction = Direction.TOP_BOTTOM,
align,
nodeMargin = 50,
edgeMargin = 10,
rankMargin = 50,
paddingX = 0,
paddingY = 0,
acyclicer,
ranker = Ranker.NETWORK_SIMPLEX,
labelPosition = LabelPosition.RIGHT,
labelOffset = 10,
edgeRanks = 1,
edgeWeight = 1,
renderLabel,
defs,
...svgProps
} = props;
const theme: BackstageTheme = useTheme();
const [containerWidth, setContainerWidth] = React.useState<number>(100);
const [containerHeight, setContainerHeight] = React.useState<number>(100);
@@ -70,12 +70,8 @@ type Props = {
fixed?: boolean;
};
export const DismissableBanner = ({
variant,
message,
id,
fixed = false,
}: Props) => {
export const DismissableBanner = (props: Props) => {
const { variant, message, id, fixed = false } = props;
const classes = useStyles();
const storageApi = useApi(storageApiRef);
const notificationsStore = storageApi.forBucket('notifications');
@@ -38,7 +38,8 @@ type Props = {
action?: JSX.Element;
};
export const EmptyState = ({ title, description, missing, action }: Props) => {
export function EmptyState(props: Props) {
const { title, description, missing, action } = props;
const classes = useStyles();
return (
<Grid
@@ -67,4 +68,4 @@ export const EmptyState = ({ title, description, missing, action }: Props) => {
</Grid>
</Grid>
);
};
}
@@ -45,7 +45,8 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
},
}));
export const MissingAnnotationEmptyState = ({ annotation }: Props) => {
export function MissingAnnotationEmptyState(props: Props) {
const { annotation } = props;
const classes = useStyles();
const description = (
<>
@@ -84,4 +85,4 @@ export const MissingAnnotationEmptyState = ({ annotation }: Props) => {
}
/>
);
};
}
@@ -92,12 +92,8 @@ export type ErrorPanelProps = {
/**
* Renders a warning panel as the effect of an error.
*/
export const ErrorPanel = ({
title,
error,
defaultExpanded,
children,
}: PropsWithChildren<ErrorPanelProps>) => {
export function ErrorPanel(props: PropsWithChildren<ErrorPanelProps>) {
const { title, error, defaultExpanded, children } = props;
return (
<WarningPanel
severity="error"
@@ -112,4 +108,4 @@ export const ErrorPanel = ({
/>
</WarningPanel>
);
};
}
@@ -93,12 +93,8 @@ type Placement = {
textWidth: number;
};
export const FeatureCalloutCircular = ({
featureId,
title,
description,
children,
}: PropsWithChildren<Props>) => {
export function FeatureCalloutCircular(props: PropsWithChildren<Props>) {
const { featureId, title, description, children } = props;
const { show, hide } = useShowCallout(featureId);
const portalElement = usePortal('core.callout');
const wrapperRef = useRef<HTMLDivElement>(null);
@@ -196,4 +192,4 @@ export const FeatureCalloutCircular = ({
)}
</>
);
};
}
@@ -31,7 +31,8 @@ type Props = {
links: IconLinkVerticalProps[];
};
export const HeaderIconLinkRow = ({ links }: Props) => {
export function HeaderIconLinkRow(props: Props) {
const { links } = props;
const classes = useStyles();
return (
<nav className={classes.links}>
@@ -40,4 +41,4 @@ export const HeaderIconLinkRow = ({ links }: Props) => {
))}
</nav>
);
};
}
@@ -181,7 +181,7 @@ function useSmoothScroll(
return setScrollTarget;
}
export const HorizontalScrollGrid = (props: PropsWithChildren<Props>) => {
export function HorizontalScrollGrid(props: PropsWithChildren<Props>) {
const {
scrollStep = 100,
scrollSpeed = 50,
@@ -244,4 +244,4 @@ export const HorizontalScrollGrid = (props: PropsWithChildren<Props>) => {
)}
</div>
);
};
}
@@ -38,7 +38,7 @@ const useStyles = makeStyles({
},
});
export const Lifecycle = (props: Props) => {
export function Lifecycle(props: Props) {
const classes = useStyles(props);
const { shorthand, alpha } = props;
return shorthand ? (
@@ -53,4 +53,4 @@ export const Lifecycle = (props: Props) => {
{alpha ? 'Alpha' : 'Beta'}
</span>
);
};
}
@@ -31,11 +31,13 @@ export type LinkProps = MaterialLinkProps &
component?: ElementType<any>;
};
declare function LinkType(props: LinkProps): JSX.Element;
/**
* Thin wrapper on top of material-ui's Link component
* Makes the Link to utilise react-router
*/
export const Link = React.forwardRef<any, LinkProps>((props, ref) => {
const ActualLink = React.forwardRef<any, LinkProps>((props, ref) => {
const to = String(props.to);
const external = isExternalUri(to);
const newWindow = external && !!/^https?:/.exec(to);
@@ -52,3 +54,10 @@ export const Link = React.forwardRef<any, LinkProps>((props, ref) => {
<MaterialLink ref={ref} component={RouterLink} {...props} />
);
});
// TODO(Rugvip): We use this as a workaround to make the exported type be a
// function, which makes our API reference docs much nicer.
// The first type to be exported gets priority, but it will
// be thrown away when compiling to JS.
// @ts-ignore
export { LinkType as Link, ActualLink as Link };
@@ -76,7 +76,8 @@ const renderers = {
* Renders markdown with the default dialect [gfm - GitHub flavored Markdown](https://github.github.com/gfm/) to backstage theme styled HTML.
* If you just want to render to plain [CommonMark](https://commonmark.org/), set the dialect to `'common-mark'`
*/
export const MarkdownContent = ({ content, dialect = 'gfm' }: Props) => {
export function MarkdownContent(props: Props) {
const { content, dialect = 'gfm' } = props;
const classes = useStyles();
return (
<ReactMarkdown
@@ -86,4 +87,4 @@ export const MarkdownContent = ({ content, dialect = 'gfm' }: Props) => {
renderers={renderers}
/>
);
};
}
@@ -41,7 +41,7 @@ const useStyles = makeStyles<Theme>(theme => ({
},
}));
export const OAuthRequestDialog = () => {
export function OAuthRequestDialog(_props: {}) {
const classes = useStyles();
const [busy, setBusy] = useState(false);
const oauthRequestApi = useApi(oauthRequestApiRef);
@@ -83,4 +83,4 @@ export const OAuthRequestDialog = () => {
</DialogActions>
</Dialog>
);
};
}
@@ -32,7 +32,7 @@ const useStyles = makeStyles({
},
});
export const OverflowTooltip = (props: Props) => {
export function OverflowTooltip(props: Props) {
const [hover, setHover] = useState(false);
const classes = useStyles();
@@ -54,4 +54,4 @@ export const OverflowTooltip = (props: Props) => {
/>
</Tooltip>
);
};
}
@@ -17,7 +17,7 @@
import React, { useState, useEffect, PropsWithChildren } from 'react';
import { LinearProgress, LinearProgressProps } from '@material-ui/core';
export const Progress = (props: PropsWithChildren<LinearProgressProps>) => {
export function Progress(props: PropsWithChildren<LinearProgressProps>) {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
@@ -30,4 +30,4 @@ export const Progress = (props: PropsWithChildren<LinearProgressProps>) => {
) : (
<div style={{ display: 'none' }} />
);
};
}
@@ -77,7 +77,7 @@ export function getProgressColor(
return palette.status.ok;
}
export const Gauge = (props: Props) => {
export function Gauge(props: Props) {
const classes = useStyles(props);
const theme = useTheme<BackstageTheme>();
const { value, fractional, inverse, unit, max } = {
@@ -103,4 +103,4 @@ export const Gauge = (props: Props) => {
</div>
</div>
);
};
}
@@ -37,7 +37,7 @@ const useStyles = makeStyles({
},
});
export const GaugeCard = (props: Props) => {
export function GaugeCard(props: Props) {
const classes = useStyles(props);
const { title, subheader, progress, inverse, deepLink, variant } = props;
@@ -53,4 +53,4 @@ export const GaugeCard = (props: Props) => {
</InfoCard>
</div>
);
};
}
@@ -28,7 +28,8 @@ type Props = {
value: number;
};
export const LinearGauge = ({ value }: Props) => {
export function LinearGauge(props: Props) {
const { value } = props;
const theme = useTheme<BackstageTheme>();
if (isNaN(value)) {
return null;
@@ -50,4 +51,4 @@ export const LinearGauge = ({ value }: Props) => {
</span>
</Tooltip>
);
};
}
@@ -39,11 +39,8 @@ const useStyles = makeStyles(theme => ({
* Has special treatment for ResponseError errors, to display rich
* server-provided information about what happened.
*/
export const ResponseErrorPanel = ({
title,
error,
defaultExpanded,
}: ErrorPanelProps) => {
export function ResponseErrorPanel(props: ErrorPanelProps) {
const { title, error, defaultExpanded } = props;
const classes = useStyles();
if (error.name !== 'ResponseError') {
@@ -93,4 +90,4 @@ export const ResponseErrorPanel = ({
</>
</ErrorPanel>
);
};
}
@@ -106,15 +106,16 @@ export type SelectProps = {
triggerReset?: boolean;
};
export const SelectComponent = ({
multiple,
items,
label,
placeholder,
selected,
onChange,
triggerReset,
}: SelectProps) => {
export function SelectComponent(props: SelectProps) {
const {
multiple,
items,
label,
placeholder,
selected,
onChange,
triggerReset,
} = props;
const classes = useStyles();
const [value, setValue] = useState<Selection>(
selected || (multiple ? [] : ''),
@@ -228,4 +229,4 @@ export const SelectComponent = ({
</ClickAwayListener>
</div>
);
};
}
@@ -47,12 +47,8 @@ export interface StepperProps {
activeStep?: number;
}
export const SimpleStepper = ({
children,
elevated,
onStepChange,
activeStep = 0,
}: PropsWithChildren<StepperProps>) => {
export function SimpleStepper(props: PropsWithChildren<StepperProps>) {
const { children, elevated, onStepChange, activeStep = 0 } = props;
const [stepIndex, setStepIndex] = useState<number>(activeStep);
const [stepHistory, setStepHistory] = useState<number[]>([0]);
@@ -95,4 +91,4 @@ export const SimpleStepper = ({
{stepIndex >= Children.count(children) - 1 && endStep}
</>
);
};
}
@@ -30,13 +30,8 @@ const useStyles = makeStyles(theme => ({
},
}));
export const SimpleStepperStep = ({
title,
children,
end,
actions,
...muiProps
}: PropsWithChildren<StepProps>) => {
export function SimpleStepperStep(props: PropsWithChildren<StepProps>) {
const { title, children, end, actions, ...muiProps } = props;
const classes = useStyles();
// The end step is not a part of the stepper
@@ -58,4 +53,4 @@ export const SimpleStepperStep = ({
</StepContent>
</MuiStep>
);
};
}
@@ -63,7 +63,7 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
},
}));
export const StatusOK = (props: PropsWithChildren<{}>) => {
export function StatusOK(props: PropsWithChildren<{}>) {
const classes = useStyles(props);
return (
<span
@@ -73,9 +73,9 @@ export const StatusOK = (props: PropsWithChildren<{}>) => {
{...props}
/>
);
};
}
export const StatusWarning = (props: PropsWithChildren<{}>) => {
export function StatusWarning(props: PropsWithChildren<{}>) {
const classes = useStyles(props);
return (
<span
@@ -85,9 +85,9 @@ export const StatusWarning = (props: PropsWithChildren<{}>) => {
{...props}
/>
);
};
}
export const StatusError = (props: PropsWithChildren<{}>) => {
export function StatusError(props: PropsWithChildren<{}>) {
const classes = useStyles(props);
return (
<span
@@ -97,9 +97,9 @@ export const StatusError = (props: PropsWithChildren<{}>) => {
{...props}
/>
);
};
}
export const StatusPending = (props: PropsWithChildren<{}>) => {
export function StatusPending(props: PropsWithChildren<{}>) {
const classes = useStyles(props);
return (
<span
@@ -109,9 +109,9 @@ export const StatusPending = (props: PropsWithChildren<{}>) => {
{...props}
/>
);
};
}
export const StatusRunning = (props: PropsWithChildren<{}>) => {
export function StatusRunning(props: PropsWithChildren<{}>) {
const classes = useStyles(props);
return (
<span
@@ -121,9 +121,9 @@ export const StatusRunning = (props: PropsWithChildren<{}>) => {
{...props}
/>
);
};
}
export const StatusAborted = (props: PropsWithChildren<{}>) => {
export function StatusAborted(props: PropsWithChildren<{}>) {
const classes = useStyles(props);
return (
<span
@@ -133,4 +133,4 @@ export const StatusAborted = (props: PropsWithChildren<{}>) => {
{...props}
/>
);
};
}
@@ -153,11 +153,8 @@ type Props = {
options?: any;
};
export const StructuredMetadataTable = ({
metadata,
dense = true,
options,
}: Props) => {
export function StructuredMetadataTable(props: Props) {
const { metadata, dense = true, options } = props;
const metadataItems = mapToItems(metadata, options || {});
return <MetadataTable dense={dense}>{metadataItems}</MetadataTable>;
};
}
@@ -80,7 +80,8 @@ const SupportListItem = ({ item }: { item: SupportItem }) => {
);
};
export const SupportButton = ({ title, children }: SupportButtonProps) => {
export function SupportButton(props: SupportButtonProps) {
const { title, children } = props;
const { items } = useSupportConfig();
const [popoverOpen, setPopoverOpen] = useState(false);
@@ -160,4 +161,4 @@ export const SupportButton = ({ title, children }: SupportButtonProps) => {
</Popover>
</>
);
};
}
@@ -47,7 +47,8 @@ export function useSelectedSubRoute(subRoutes: SubRoute[]): {
};
}
export const RoutedTabs = ({ routes }: { routes: SubRoute[] }) => {
export function RoutedTabs(props: { routes: SubRoute[] }) {
const { routes } = props;
const navigate = useNavigate();
const { index, route, element } = useSelectedSubRoute(routes);
const headerTabs = useMemo(
@@ -80,4 +81,4 @@ export const RoutedTabs = ({ routes }: { routes: SubRoute[] }) => {
</Content>
</>
);
};
}
@@ -82,10 +82,10 @@ export function createSubRoutesFromChildren(
* </TabbedLayout>
* ```
*/
export const TabbedLayout = ({ children }: PropsWithChildren<{}>) => {
const routes = createSubRoutesFromChildren(children);
export function TabbedLayout(props: PropsWithChildren<{}>) {
const routes = createSubRoutesFromChildren(props.children);
return <RoutedTabs routes={routes} />;
};
}
TabbedLayout.Route = Route;
@@ -33,7 +33,8 @@ type SubvalueCellProps = {
subvalue: React.ReactNode;
};
export const SubvalueCell = ({ value, subvalue }: SubvalueCellProps) => {
export function SubvalueCell(props: SubvalueCellProps) {
const { value, subvalue } = props;
const classes = useSubvalueCellStyles();
return (
@@ -42,4 +43,4 @@ export const SubvalueCell = ({ value, subvalue }: SubvalueCellProps) => {
<div className={classes.subvalue}>{subvalue}</div>
</>
);
};
}
@@ -263,21 +263,21 @@ export function TableToolbar(toolbarProps: {
);
}
export function Table<T extends object = {}>({
columns,
options,
title,
subtitle,
filters,
initialState,
emptyContent,
onStateChange,
...props
}: TableProps<T>) {
export function Table<T extends object = {}>(props: TableProps<T>) {
const {
data,
columns,
options,
title,
subtitle,
filters,
initialState,
emptyContent,
onStateChange,
...restProps
} = props;
const tableClasses = useTableStyles();
const { data, ...propsWithoutData } = props;
const theme = useTheme<BackstageTheme>();
const calculatedInitialState = { ...defaultInitialState, ...initialState };
@@ -495,7 +495,7 @@ export function Table<T extends object = {}>({
}
data={typeof data === 'function' ? data : tableData}
style={{ width: '100%' }}
{...propsWithoutData}
{...restProps}
/>
</div>
);
@@ -58,7 +58,8 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
},
}));
export const Tabs = ({ tabs }: TabsProps) => {
export function Tabs(props: TabsProps) {
const { tabs } = props;
const classes = useStyles();
const [value, setValue] = useState([0, 0]); // [selectedChunkedNavIndex, selectedIndex]
const [navIndex, setNavIndex] = useState(0);
@@ -160,4 +161,4 @@ export const Tabs = ({ tabs }: TabsProps) => {
)}
</div>
);
};
}
@@ -32,10 +32,10 @@ function color(data: number[], theme: BackstageTheme): string | undefined {
return theme.palette.status.error;
}
export const TrendLine = (
export function TrendLine(
props: SparklinesProps &
Pick<SparklinesLineProps, 'color'> & { title?: string },
) => {
) {
const theme = useTheme<BackstageTheme>();
if (!props.data) return null;
@@ -45,4 +45,4 @@ export const TrendLine = (
<SparklinesLine color={props.color ?? color(props.data, theme)} />
</Sparklines>
);
};
}
@@ -137,13 +137,14 @@ const capitalize = (s: string) => {
* @param {Object} [children] Objects to provide context, such as a stack trace or detailed error reporting.
* Will be available inside an unfolded accordion.
*/
export const WarningPanel = ({
severity = 'warning',
title,
message,
children,
defaultExpanded,
}: WarningProps) => {
export function WarningPanel(props: WarningProps) {
const {
severity = 'warning',
title,
message,
children,
defaultExpanded,
} = props;
const classes = useStyles({ severity });
// If no severity or title provided, the heading will read simply "Warning"
@@ -184,4 +185,4 @@ export const WarningPanel = ({
)}
</Accordion>
);
};
}