mirror of
https://github.com/Unleash/unleash.git
synced 2025-11-10 01:19:53 +01:00
Based on this article: https://mui.com/material-ui/guides/minimizing-bundle-size/ importing from `'@mui/icons-material'` instead of specifying the actual path to the icon like `import Delete from '@mui/icons-material/Delete';` can be up to six time slower. This change changes all named imports in Unleash referencing the `@mui/icons-material` to default imports. This reduced the amount of modules we had to process when building the frontend from 15206 to 4746 Before: <img width="1016" alt="Skjermbilde 2024-03-11 kl 14 19 58" src="https://github.com/Unleash/unleash/assets/16081982/f137d24a-6557-4183-a40f-f62a33524520"> After: <img width="1237" alt="Skjermbilde 2024-03-11 kl 14 20 32" src="https://github.com/Unleash/unleash/assets/16081982/05a27d6a-2c3f-4409-9862-7188ab4b9c72"> Build time locally decreased by around 50% Before: <img width="1504" alt="Skjermbilde 2024-03-11 kl 14 31 45" src="https://github.com/Unleash/unleash/assets/16081982/bc931559-b022-47ed-9f8f-c87401578518"> After: <img width="1219" alt="Skjermbilde 2024-03-11 kl 14 27 00" src="https://github.com/Unleash/unleash/assets/16081982/3c3a8d6b-576d-45c3-aa40-cc5f95d9df2b">
433 lines
17 KiB
TypeScript
433 lines
17 KiB
TypeScript
import { Button, Checkbox, FormControlLabel, styled } from '@mui/material';
|
|
import { Banner } from 'component/banners/Banner/Banner';
|
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
import { FormSwitch } from 'component/common/FormSwitch/FormSwitch';
|
|
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
|
|
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
|
|
import Input from 'component/common/Input/Input';
|
|
import { BannerVariant } from 'interfaces/banner';
|
|
import { ChangeEvent, Dispatch, SetStateAction, useState } from 'react';
|
|
import Visibility from '@mui/icons-material/Visibility';
|
|
import { BannerDialog } from 'component/banners/Banner/BannerDialog/BannerDialog';
|
|
|
|
const StyledForm = styled('div')(({ theme }) => ({
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: theme.spacing(4),
|
|
}));
|
|
|
|
const StyledBannerPreview = styled('div')(({ theme }) => ({
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
padding: theme.spacing(1.5),
|
|
gap: theme.spacing(1.5),
|
|
border: `1px solid ${theme.palette.divider}`,
|
|
borderRadius: theme.shape.borderRadiusMedium,
|
|
}));
|
|
|
|
const StyledBannerPreviewDescription = styled('p')(({ theme }) => ({
|
|
fontSize: theme.fontSizes.smallBody,
|
|
color: theme.palette.text.secondary,
|
|
}));
|
|
|
|
const StyledRaisedSection = styled('div')(({ theme }) => ({
|
|
background: theme.palette.background.elevation1,
|
|
padding: theme.spacing(2, 3),
|
|
borderRadius: theme.shape.borderRadiusLarge,
|
|
}));
|
|
|
|
const StyledSection = styled('div')(({ theme }) => ({
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: theme.spacing(1.5),
|
|
}));
|
|
|
|
const StyledSectionLabel = styled('p')(({ theme }) => ({
|
|
fontWeight: theme.fontWeight.bold,
|
|
}));
|
|
|
|
const StyledFieldGroup = styled('div')(({ theme }) => ({
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: theme.spacing(1),
|
|
}));
|
|
|
|
const StyledInputDescription = styled('p')(({ theme }) => ({
|
|
display: 'flex',
|
|
color: theme.palette.text.primary,
|
|
}));
|
|
|
|
const StyledInput = styled(Input)({
|
|
width: '100%',
|
|
});
|
|
|
|
const StyledTooltip = styled('div')(({ theme }) => ({
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
padding: theme.spacing(0.5),
|
|
gap: theme.spacing(0.5),
|
|
}));
|
|
|
|
const StyledSelect = styled(GeneralSelect)(({ theme }) => ({
|
|
width: '100%',
|
|
maxWidth: theme.spacing(50),
|
|
}));
|
|
|
|
const StyledPreviewButton = styled(Button)(({ theme }) => ({
|
|
marginRight: 'auto',
|
|
}));
|
|
|
|
const VARIANT_OPTIONS = [
|
|
{ key: 'info', label: 'Information' },
|
|
{ key: 'warning', label: 'Warning' },
|
|
{ key: 'error', label: 'Error' },
|
|
{ key: 'success', label: 'Success' },
|
|
];
|
|
|
|
type IconOption = 'Default' | 'Custom' | 'None';
|
|
type LinkOption = 'Link' | 'Dialog' | 'None';
|
|
|
|
interface IBannerFormProps {
|
|
enabled: boolean;
|
|
message: string;
|
|
variant: BannerVariant;
|
|
sticky: boolean;
|
|
icon: string;
|
|
link: string;
|
|
linkText: string;
|
|
dialogTitle: string;
|
|
dialog: string;
|
|
setEnabled: Dispatch<SetStateAction<boolean>>;
|
|
setMessage: Dispatch<SetStateAction<string>>;
|
|
setVariant: Dispatch<SetStateAction<BannerVariant>>;
|
|
setSticky: Dispatch<SetStateAction<boolean>>;
|
|
setIcon: Dispatch<SetStateAction<string>>;
|
|
setLink: Dispatch<SetStateAction<string>>;
|
|
setLinkText: Dispatch<SetStateAction<string>>;
|
|
setDialogTitle: Dispatch<SetStateAction<string>>;
|
|
setDialog: Dispatch<SetStateAction<string>>;
|
|
}
|
|
|
|
export const BannerForm = ({
|
|
enabled,
|
|
message,
|
|
variant,
|
|
sticky,
|
|
icon,
|
|
link,
|
|
linkText,
|
|
dialogTitle,
|
|
dialog,
|
|
setEnabled,
|
|
setMessage,
|
|
setVariant,
|
|
setSticky,
|
|
setIcon,
|
|
setLink,
|
|
setLinkText,
|
|
setDialogTitle,
|
|
setDialog,
|
|
}: IBannerFormProps) => {
|
|
const [previewDialogOpen, setPreviewDialogOpen] = useState(false);
|
|
|
|
const [iconOption, setIconOption] = useState<IconOption>(
|
|
icon === '' ? 'Default' : icon === 'none' ? 'None' : 'Custom',
|
|
);
|
|
const [linkOption, setLinkOption] = useState<LinkOption>(
|
|
link === '' ? 'None' : link === 'dialog' ? 'Dialog' : 'Link',
|
|
);
|
|
|
|
return (
|
|
<StyledForm>
|
|
<StyledBannerPreview>
|
|
<StyledBannerPreviewDescription>
|
|
Banner preview:
|
|
</StyledBannerPreviewDescription>
|
|
<Banner
|
|
banner={{
|
|
message:
|
|
message ||
|
|
'*No message set. Please enter a message below.*',
|
|
variant,
|
|
sticky: false,
|
|
icon,
|
|
link,
|
|
linkText,
|
|
dialogTitle,
|
|
dialog,
|
|
}}
|
|
inline
|
|
/>
|
|
</StyledBannerPreview>
|
|
<StyledRaisedSection>
|
|
<FormSwitch checked={enabled} setChecked={setEnabled}>
|
|
Banner status
|
|
</FormSwitch>
|
|
</StyledRaisedSection>
|
|
<StyledSection>
|
|
<StyledSectionLabel>Configuration</StyledSectionLabel>
|
|
<StyledFieldGroup>
|
|
<StyledInputDescription>
|
|
What type of banner is it?
|
|
</StyledInputDescription>
|
|
<StyledSelect
|
|
size='small'
|
|
value={variant}
|
|
onChange={(variant) =>
|
|
setVariant(variant as BannerVariant)
|
|
}
|
|
options={VARIANT_OPTIONS}
|
|
/>
|
|
</StyledFieldGroup>
|
|
<StyledFieldGroup>
|
|
<StyledInputDescription>
|
|
What icon should be displayed on the banner?
|
|
</StyledInputDescription>
|
|
<StyledSelect
|
|
size='small'
|
|
value={iconOption}
|
|
onChange={(iconOption) => {
|
|
setIconOption(iconOption as IconOption);
|
|
if (iconOption === 'None') {
|
|
setIcon('none');
|
|
} else {
|
|
setIcon('');
|
|
}
|
|
}}
|
|
options={['Default', 'Custom', 'None'].map(
|
|
(option) => ({
|
|
key: option,
|
|
label: option,
|
|
}),
|
|
)}
|
|
/>
|
|
</StyledFieldGroup>
|
|
<ConditionallyRender
|
|
condition={iconOption === 'Custom'}
|
|
show={
|
|
<StyledFieldGroup>
|
|
<StyledInputDescription>
|
|
Which custom icon?
|
|
<HelpIcon
|
|
htmlTooltip
|
|
tooltip={
|
|
<StyledTooltip>
|
|
<p>
|
|
Choose an icon from{' '}
|
|
<a
|
|
href='https://fonts.google.com/icons'
|
|
target='_blank'
|
|
rel='noreferrer'
|
|
>
|
|
Material Symbols
|
|
</a>
|
|
.
|
|
</p>
|
|
<p>
|
|
For example, if you want to
|
|
display the "Rocket Launch"
|
|
icon, you can enter
|
|
"rocket_launch" in the field
|
|
below.
|
|
</p>
|
|
</StyledTooltip>
|
|
}
|
|
/>
|
|
</StyledInputDescription>
|
|
<StyledInput
|
|
label='Banner icon'
|
|
value={icon}
|
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
setIcon(e.target.value)
|
|
}
|
|
autoComplete='off'
|
|
/>
|
|
</StyledFieldGroup>
|
|
}
|
|
/>
|
|
<StyledFieldGroup>
|
|
<StyledInputDescription>
|
|
What is your banner message?
|
|
<HelpIcon
|
|
htmlTooltip
|
|
tooltip={
|
|
<StyledTooltip>
|
|
<p>
|
|
<a
|
|
href='https://www.markdownguide.org/basic-syntax/'
|
|
target='_blank'
|
|
rel='noreferrer'
|
|
>
|
|
Markdown
|
|
</a>{' '}
|
|
is supported.
|
|
</p>
|
|
</StyledTooltip>
|
|
}
|
|
/>
|
|
</StyledInputDescription>
|
|
<StyledInput
|
|
autoFocus
|
|
label='Banner message'
|
|
multiline
|
|
minRows={2}
|
|
maxRows={6}
|
|
value={message}
|
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
setMessage(e.target.value)
|
|
}
|
|
autoComplete='off'
|
|
required
|
|
/>
|
|
</StyledFieldGroup>
|
|
</StyledSection>
|
|
<StyledSection>
|
|
<StyledSectionLabel>Banner action</StyledSectionLabel>
|
|
<StyledFieldGroup>
|
|
<StyledInputDescription>
|
|
What action should be available in the banner?
|
|
</StyledInputDescription>
|
|
<StyledSelect
|
|
size='small'
|
|
value={linkOption}
|
|
onChange={(linkOption) => {
|
|
setLinkOption(linkOption as LinkOption);
|
|
if (linkOption === 'Dialog') {
|
|
setLink('dialog');
|
|
} else {
|
|
setLink('');
|
|
}
|
|
setLinkText('');
|
|
setDialogTitle('');
|
|
setDialog('');
|
|
}}
|
|
options={['None', 'Link', 'Dialog'].map((option) => ({
|
|
key: option,
|
|
label: option,
|
|
}))}
|
|
/>
|
|
</StyledFieldGroup>
|
|
<ConditionallyRender
|
|
condition={linkOption === 'Link'}
|
|
show={
|
|
<StyledFieldGroup>
|
|
<StyledInputDescription>
|
|
What URL should be opened?
|
|
</StyledInputDescription>
|
|
<StyledInput
|
|
label='URL'
|
|
value={link}
|
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
setLink(e.target.value)
|
|
}
|
|
onBlur={() => {
|
|
if (!linkText) setLinkText(link);
|
|
}}
|
|
autoComplete='off'
|
|
/>
|
|
</StyledFieldGroup>
|
|
}
|
|
/>
|
|
<ConditionallyRender
|
|
condition={linkOption !== 'None'}
|
|
show={
|
|
<StyledFieldGroup>
|
|
<StyledInputDescription>
|
|
What is the action text?
|
|
</StyledInputDescription>
|
|
<StyledInput
|
|
label='Action text'
|
|
value={linkText}
|
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
setLinkText(e.target.value)
|
|
}
|
|
autoComplete='off'
|
|
/>
|
|
</StyledFieldGroup>
|
|
}
|
|
/>
|
|
<ConditionallyRender
|
|
condition={linkOption === 'Dialog'}
|
|
show={
|
|
<>
|
|
<StyledFieldGroup>
|
|
<StyledInputDescription>
|
|
What is the dialog title?
|
|
</StyledInputDescription>
|
|
<StyledInput
|
|
label='Dialog title'
|
|
value={dialogTitle}
|
|
onChange={(
|
|
e: ChangeEvent<HTMLInputElement>,
|
|
) => setDialogTitle(e.target.value)}
|
|
autoComplete='off'
|
|
/>
|
|
</StyledFieldGroup>
|
|
<StyledFieldGroup>
|
|
<StyledInputDescription>
|
|
What is the dialog content?
|
|
<HelpIcon
|
|
htmlTooltip
|
|
tooltip={
|
|
<StyledTooltip>
|
|
<p>
|
|
<a
|
|
href='https://www.markdownguide.org/basic-syntax/'
|
|
target='_blank'
|
|
rel='noreferrer'
|
|
>
|
|
Markdown
|
|
</a>{' '}
|
|
is supported.
|
|
</p>
|
|
</StyledTooltip>
|
|
}
|
|
/>
|
|
</StyledInputDescription>
|
|
<StyledInput
|
|
label='Dialog content'
|
|
multiline
|
|
minRows={4}
|
|
value={dialog}
|
|
onChange={(
|
|
e: ChangeEvent<HTMLInputElement>,
|
|
) => setDialog(e.target.value)}
|
|
autoComplete='off'
|
|
/>
|
|
</StyledFieldGroup>
|
|
<StyledPreviewButton
|
|
variant='outlined'
|
|
color='primary'
|
|
startIcon={<Visibility />}
|
|
onClick={() => setPreviewDialogOpen(true)}
|
|
>
|
|
Preview dialog
|
|
</StyledPreviewButton>
|
|
<BannerDialog
|
|
open={previewDialogOpen}
|
|
setOpen={setPreviewDialogOpen}
|
|
title={dialogTitle || linkText}
|
|
>
|
|
{dialog!}
|
|
</BannerDialog>
|
|
</>
|
|
}
|
|
/>
|
|
</StyledSection>
|
|
<StyledSection>
|
|
<StyledSectionLabel>Sticky banner</StyledSectionLabel>
|
|
<FormControlLabel
|
|
control={
|
|
<Checkbox
|
|
checked={sticky}
|
|
onChange={(e) => setSticky(e.target.checked)}
|
|
/>
|
|
}
|
|
label='Make the banner sticky on the screen when scrolling'
|
|
/>
|
|
</StyledSection>
|
|
</StyledForm>
|
|
);
|
|
};
|