@material-ui/icons#ExpandMore TypeScript Examples
The following examples show how to use
@material-ui/icons#ExpandMore.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: DiscloseSlippageSelector.tsx From anchor-web-app with Apache License 2.0 | 6 votes |
function Component({
className,
value,
...selectorProps
}: DiscloseSlippageSelectorProps) {
const [{ open }, setOpen] = useLocalStorage<{ open: boolean }>(
'__anchor_slippage__',
{ open: false },
);
return (
<details className={className} {...(open ? { open: true } : {})}>
<summary
onClick={(event) => {
event.preventDefault();
setOpen({ open: !open });
}}
>
{open ? <ExpandLess /> : <ExpandMore />}
<IconSpan>
Slippage Tolerance{' '}
<InfoTooltip>
The transaction will revert if the price changes by more than the
defined percentage.{' '}
</InfoTooltip>
: {big(value).mul(100).toFixed()}%
</IconSpan>
</summary>
<SlippageSelector value={value} {...selectorProps} className="selector" />
</details>
);
}
Example #2
Source File: ExpandableElement.tsx From bee-dashboard with BSD 3-Clause "New" or "Revised" License | 6 votes |
export default function ExpandableElement({ children, expandable, defaultOpen }: Props): ReactElement | null {
const classes = useStyles()
const [open, setOpen] = useState<boolean>(Boolean(defaultOpen))
const handleClick = () => {
setOpen(!open)
}
return (
<div className={`${classes.root} ${classes.rootLevel2}`}>
<ListItem button onClick={handleClick} className={classes.header}>
{children}
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<div className={classes.contentLevel12}>{expandable}</div>
</Collapse>
</div>
)
}
Example #3
Source File: Swap.tsx From swap-ui with Apache License 2.0 | 6 votes |
function TokenButton({
mint,
onClick,
}: {
mint: PublicKey;
onClick: () => void;
}) {
const styles = useStyles();
const theme = useTheme();
return (
<div onClick={onClick} className={styles.tokenButton}>
<TokenIcon mint={mint} style={{ width: theme.spacing(4) }} />
<TokenName mint={mint} style={{ fontSize: 14, fontWeight: 700 }} />
<ExpandMore />
</div>
);
}
Example #4
Source File: MoreMenu.tsx From anchor-web-app with Apache License 2.0 | 5 votes |
function MoreMenuBase({ children, className }: MoreMenuProps) {
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
return (
<>
<MoreButton
onClick={(event: MouseEvent<HTMLButtonElement>) =>
setAnchorEl(event.currentTarget)
}
>
More {anchorEl ? <ExpandLess /> : <ExpandMore />}
</MoreButton>
<Popover
open={!!anchorEl}
anchorEl={anchorEl}
onClose={() => setAnchorEl(null)}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
className={className}
>
<MenuList variant="menu">
{Children.map(children, (child) => {
return cloneElement(child, {
children: (
<IconSpan>
{child.props.children} <ChevronRightRounded />
</IconSpan>
),
});
})}
</MenuList>
</Popover>
</>
);
}
Example #5
Source File: GovBanner.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 5 votes |
GovBanner: React.FC = () => {
const classes = useStyles();
const [expanded, setExpanded] = useState(false);
return (
<>
<div className={classes.root}>
<div className={classes.inner}>
<img src={flagIcon} alt="usa flag" className={classes.flag} />
<div className={classes.textWrap}>
<div className={classes.text}>
An official website of the United States government
</div>
<button
className={clsx(classes.text, classes.btn, classes.btnExpand)}
onClick={() => setExpanded((exp) => !exp)}
>
Here's how you know{' '}
{expanded ? (
<ExpandLess fontSize="small" />
) : (
<ExpandMore fontSize="small" />
)}
</button>
</div>
</div>
</div>
{expanded && (
<div className={classes.root}>
<div className={clsx(classes.inner, classes.infoInner)}>
<div className={classes.info}>
<div className={classes.infoIcon}>
<img src={govIcon} alt="Dot Gov" />
</div>
<div className={classes.infoText}>
<p>
<strong>The .gov means it’s official.</strong>
<br />
Federal government websites often end in .gov or .mil. Before
sharing sensitive information, make sure you’re on a federal
government site.
</p>
</div>
</div>
<div className={classes.info}>
<div className={classes.infoIcon}>
<img src={httpsIcon} alt="HTTPS" />
</div>
<div className={classes.infoText}>
<p>
<strong>This site is secure.</strong>
<br />
The <strong>https://</strong> ensures that you are connecting
to the official website and that any information you provide
is encrypted and transmitted securely.
</p>
</div>
</div>
</div>
</div>
)}
</>
);
}
Example #6
Source File: ExpandableList.tsx From bee-dashboard with BSD 3-Clause "New" or "Revised" License | 5 votes |
export default function ExpandableList({ children, label, level, defaultOpen, info }: Props): ReactElement | null {
const classes = useStyles()
const [open, setOpen] = useState<boolean>(Boolean(defaultOpen))
const handleClick = () => {
setOpen(!open)
}
let rootLevelClass = ''
let typographyVariant: 'h1' | 'h2' | 'h3' = 'h1'
let contentLevelClass = classes.contentLevel0
if (level === 1) {
rootLevelClass = classes.rootLevel1
typographyVariant = 'h2'
contentLevelClass = classes.contentLevel12
} else if (level === 2) {
rootLevelClass = classes.rootLevel2
typographyVariant = 'h3'
contentLevelClass = classes.contentLevel12
}
return (
<div className={`${classes.root} ${rootLevelClass}`}>
<ListItem button onClick={handleClick} className={classes.header}>
<ListItemText primary={<Typography variant={typographyVariant}>{label}</Typography>} />
<div style={{ display: 'flex' }}>
{!open && (
<Typography variant="body2" className={classes.infoText}>
{info}
</Typography>
)}
{open ? <ExpandLess /> : <ExpandMore />}
</div>
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<div className={contentLevelClass}>{children}</div>
</Collapse>
</div>
)
}
Example #7
Source File: Scores.tsx From dashboard with Apache License 2.0 | 5 votes |
Scores = ({ score, nested }: ScoreProps) => {
const [show, setShow] = useState(false)
const { palette } = useTheme()
let { op_name, operands, value, ref_id, description } = score
if (!op_name && !operands && !value) {
return <></>
}
const toggleShow = () => {
setShow((prev) => !prev)
}
const canToggle = operands && operands.length > 0
return (
<div style={{ paddingLeft: nested ? "10px" : "0px" }}>
<ListItem button={canToggle} onClick={toggleShow}>
<Grid container>
<Grid item xs={4}>
<Box paddingTop="0.75rem">
<Typography noWrap fontSize="1.25rem">
{score.value || "0.12341234"}
</Typography>
</Box>
</Grid>
<Grid item xs={7}>
<Box>
<Typography noWrap>{op_name}</Typography>
<Typography noWrap color={palette.grey[500]}>
{description}
</Typography>
</Box>
</Grid>
</Grid>
{canToggle && (
<ListItemSecondaryAction>
({operands?.length}){show ? <ExpandLess /> : <ExpandMore />}
</ListItemSecondaryAction>
)}
</ListItem>
{operands && (
<div>
<Collapse in={show}>
<List>
{operands.map((operand, index) => (
<Scores score={operand} key={`${index}-${ref_id}`} nested />
))}
</List>
</Collapse>
</div>
)}
</div>
)
}
Example #8
Source File: SwaggerView.tsx From dashboard with Apache License 2.0 | 5 votes |
SwaggerView = () => {
const [show, setShow] = useState(false)
const [host, setHost] = useState(getInitialHostAndPort().host)
const [port, setPort] = useState(getInitialHostAndPort().port)
const [endpoint, setEndpoint] = useState(DEFAULT_ENDPOINT)
const [url, setURL] = useState(formatURL(host, port, endpoint))
const updateURL = () => {
setURL(formatURL(host, port, endpoint))
}
const toggleShow = () => {
setShow((prev) => !prev)
}
return (
<Container data-name="debuggingTool">
<Box textAlign="right">
<Button onClick={toggleShow}>
endpoint settings {show ? <ExpandLess /> : <ExpandMore />}
</Button>
</Box>
<Collapse in={show}>
<Box paddingTop="2.5em">
<Grid container spacing={2}>
<Grid item xs={4}>
<TextInput
label="Host"
variant="outlined"
value={host}
onChange={(e) => setHost(e.target.value)}
/>
</Grid>
<Grid item xs={2}>
<TextInput
label="Port"
variant="outlined"
value={port}
onChange={(e) => setPort(e.target.value)}
/>
</Grid>
<Grid item xs={4}>
<TextInput
label="OpenAPI Schema"
variant="outlined"
value={endpoint}
onChange={(e) => setEndpoint(e.target.value)}
/>
</Grid>
<Grid item xs={2}>
<FullSizeButton onClick={updateURL} variant="contained">
Set
</FullSizeButton>
</Grid>
</Grid>
</Box>
</Collapse>
<SwaggerUIReact
url={url}
presets={[WrappedComponents]}
requestInterceptor={(r) => console.log("request:", r)}
responseInterceptor={(r) => console.log("response:", r)}
/>
</Container>
)
}
Example #9
Source File: Facets.tsx From cognitive-search-static-web-apps-sample-ui with MIT License | 5 votes |
render(): JSX.Element {
const state = this.props.state;
return (<FacetList component="nav">
{state.facets.map(facetState => {
var facetComponent: JSX.Element = null;
switch (facetState.facetType) {
case FacetTypeEnum.BooleanFacet:
facetComponent = (<BooleanFacet state={facetState.state as BooleanFacetState} inProgress={this.props.inProgress} />);
break;
case FacetTypeEnum.NumericFacet:
facetComponent = (<NumericFacet state={facetState.state as NumericFacetState} inProgress={this.props.inProgress} />);
break;
case FacetTypeEnum.DateFacet:
facetComponent = (<DateFacet state={facetState.state as DateFacetState} inProgress={this.props.inProgress} />);
break;
case FacetTypeEnum.StringFacet:
facetComponent = (<StringFacet state={facetState.state as StringFacetState} inProgress={this.props.inProgress} />);
break;
case FacetTypeEnum.StringCollectionFacet:
facetComponent = (<StringCollectionFacet state={facetState.state as StringCollectionFacetState} inProgress={this.props.inProgress} />);
break;
}
// Getting reference to a proper getHintText method in this a bit unusual and not very strongly typed way
const getHintTextFunc = facetComponent?.type.getHintText;
return (<div key={facetState.displayName}>
<FacetListItem disableRipple={true} button onClick={() => state.toggleExpand(facetState.fieldName)}>
<ListItemText
primary={facetState.displayName}
secondary={getHintTextFunc ? getHintTextFunc(facetState.state) : ''}
/>
{!!facetState.isExpanded ? <ExpandLess /> : <ExpandMore />}
</FacetListItem>
<Collapse in={facetState.isExpanded} timeout={200} unmountOnExit>
{facetComponent}
</Collapse>
</div>);
})}
</FacetList>);
}
Example #10
Source File: MetricAssignmentResults.tsx From abacus with GNU General Public License v2.0 | 4 votes |
/**
* Display results for a MetricAssignment
*/
export default function MetricAssignmentResults({
strategy,
metricAssignment,
metric,
analysesByStrategyDateAsc,
experiment,
recommendation,
variationDiffKey,
}: {
strategy: AnalysisStrategy
metricAssignment: MetricAssignment
metric: Metric
analysesByStrategyDateAsc: Record<AnalysisStrategy, Analysis[]>
experiment: ExperimentFull
recommendation: Recommendations.Recommendation
variationDiffKey: string
}): JSX.Element | null {
const classes = useStyles()
const [isShowObservedData, setIsShowObservedData] = useState<boolean>(false)
const toggleIsShowObservedData = () => {
setIsShowObservedData((isShowObservedData) => !isShowObservedData)
}
const isConversion = metric.parameterType === MetricParameterType.Conversion
const estimateTransform: (estimate: number | null) => number | null = isConversion
? (estimate: number | null) => estimate && estimate * 100
: identity
const analyses = analysesByStrategyDateAsc[strategy]
const latestAnalysis = _.last(analyses)
const latestEstimates = latestAnalysis?.metricEstimates
if (!latestAnalysis || !latestEstimates) {
return <MissingAnalysisMessage />
}
const [_changeVariationId, baseVariationId] = variationDiffKey.split('_')
const dates = analyses.map(({ analysisDatetime }) => analysisDatetime.toISOString())
const plotlyDataVariationGraph: Array<Partial<PlotData>> = [
..._.flatMap(experiment.variations, (variation, index) => {
return [
{
name: `${variation.name}: lower bound`,
x: dates,
y: analyses
.map(
({ metricEstimates }) => metricEstimates && metricEstimates.variations[variation.variationId].bottom_95,
)
.map(estimateTransform),
line: {
color: Visualizations.variantColors[index],
},
mode: 'lines' as const,
type: 'scatter' as const,
},
{
name: `${variation.name}: upper bound`,
x: dates,
y: analyses
.map(({ metricEstimates }) => metricEstimates && metricEstimates.variations[variation.variationId].top_95)
.map(estimateTransform),
line: {
color: Visualizations.variantColors[index],
},
fill: 'tonexty' as const,
fillcolor: Visualizations.variantColors[index],
mode: 'lines' as const,
type: 'scatter' as const,
},
]
}),
]
const plotlyDataDifferenceGraph: Array<Partial<PlotData>> = [
{
name: `difference: 99% lower bound`,
x: dates,
y: analyses
.map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].bottom_99)
.map(estimateTransform),
line: { width: 0 },
marker: { color: '444' },
mode: 'lines' as const,
type: 'scatter' as const,
},
{
name: `difference: 99% upper bound`,
x: dates,
y: analyses
.map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].top_99)
.map(estimateTransform),
fill: 'tonexty',
fillcolor: 'rgba(0,0,0,.2)',
line: { width: 0 },
marker: { color: '444' },
mode: 'lines' as const,
type: 'scatter' as const,
},
{
name: `difference: 95% lower bound`,
x: dates,
y: analyses
.map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].bottom_95)
.map(estimateTransform),
line: { width: 0 },
marker: { color: '444' },
mode: 'lines' as const,
type: 'scatter' as const,
},
{
name: `difference: 95% upper bound`,
x: dates,
y: analyses
.map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].top_95)
.map(estimateTransform),
fill: 'tonexty',
fillcolor: 'rgba(0,0,0,.2)',
line: { width: 0 },
marker: { color: '444' },
mode: 'lines' as const,
type: 'scatter' as const,
},
{
name: `difference: 50% lower bound`,
x: dates,
y: analyses
.map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].bottom_50)
.map(estimateTransform),
line: { width: 0 },
marker: { color: '444' },
mode: 'lines' as const,
type: 'scatter' as const,
},
{
name: `difference: 50% upper bound`,
x: dates,
y: analyses
.map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].top_50)
.map(estimateTransform),
fill: 'tonexty',
fillcolor: 'rgba(0,0,0,.2)',
line: { width: 0 },
marker: { color: '444' },
mode: 'lines' as const,
type: 'scatter' as const,
},
{
name: 'ROPE: lower bound',
x: dates,
y: analyses.map((_) => -metricAssignment.minDifference).map(estimateTransform),
line: {
color: 'rgba(0,0,0,.4)',
dash: 'dash',
},
mode: 'lines' as const,
type: 'scatter' as const,
},
{
name: 'ROPE: upper bound',
x: dates,
y: analyses.map((_) => metricAssignment.minDifference).map(estimateTransform),
line: {
color: 'rgba(0,0,0,.4)',
dash: 'dash',
},
mode: 'lines' as const,
type: 'scatter' as const,
},
]
return (
<div className={clsx(classes.root, 'analysis-detail-panel')}>
<Typography className={classes.dataTableHeader}>Summary</Typography>
<TableContainer component={Paper}>
<Table>
<TableBody>
<TableRow>
<TableCell>
<Typography variant='h5' gutterBottom className={classes.recommendation}>
<AnalysisDisplay {...{ experiment, analysis: recommendation }} />
</Typography>
{recommendation.decision === Recommendations.Decision.ManualAnalysisRequired && (
<Typography variant='body1' gutterBottom>
<strong> Different strategies are recommending conflicting variations! </strong>
</Typography>
)}
<Typography variant='body1'>
{getOverviewMessage(experiment, recommendation)}{' '}
<Link
href={`https://github.com/Automattic/experimentation-platform/wiki/Experimenter's-Guide#reading-the-data`}
target='_blank'
>
Learn more
</Link>
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography variant='body1' gutterBottom>
The absolute change in the {isConversion ? 'conversion rate' : 'ARPU'} of{' '}
<MetricValue
metricParameterType={metric.parameterType}
isDifference={true}
value={latestEstimates.diffs[variationDiffKey].bottom_95}
displayPositiveSign
displayUnit={false}
/>{' '}
to{' '}
<MetricValue
metricParameterType={metric.parameterType}
isDifference={true}
value={latestEstimates.diffs[variationDiffKey].top_95}
displayPositiveSign
/>{' '}
is {recommendation.statisticallySignificant ? '' : ' not '}
statistically different from zero because the interval
{recommendation.statisticallySignificant ? ' excludes ' : ' includes '}
zero.{' '}
{
explanationLine2[
recommendation.practicallySignificant as Recommendations.PracticalSignificanceStatus
]
}
<MetricValue
metricParameterType={metric.parameterType}
isDifference={true}
value={-metricAssignment.minDifference}
displayPositiveSign
displayUnit={false}
/>{' '}
to{' '}
<MetricValue
metricParameterType={metric.parameterType}
isDifference={true}
value={metricAssignment.minDifference}
displayPositiveSign
/>
.
</Typography>
<strong>Last analyzed:</strong>{' '}
<DatetimeText datetime={latestAnalysis.analysisDatetime} excludeTime={true} />.
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<strong>Metric description:</strong> {metric.description}
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
<Typography className={classes.dataTableHeader}>Analysis</Typography>
<TableContainer component={Paper}>
<Table className={classes.coolTable}>
<TableHead>
<TableRow>
<TableCell>Variant</TableCell>
<TableCell align='right'>
{metric.parameterType === MetricParameterType.Revenue
? 'Average revenue per user (ARPU) interval'
: 'Conversion rate interval'}
</TableCell>
<TableCell align='right'>Absolute change</TableCell>
<TableCell align='right'>Relative change (lift)</TableCell>
</TableRow>
</TableHead>
<TableBody>
{experiment.variations.map((variation) => (
<React.Fragment key={variation.variationId}>
<TableRow>
<TableCell
component='th'
scope='row'
variant='head'
valign='top'
className={clsx(classes.rowHeader, classes.headerCell, classes.credibleIntervalHeader)}
>
<span className={classes.monospace}>{variation.name}</span>
</TableCell>
<TableCell className={classes.monospace} align='right'>
<MetricValueInterval
intervalName={'the metric value'}
metricParameterType={metric.parameterType}
bottomValue={latestEstimates.variations[variation.variationId].bottom_95}
topValue={latestEstimates.variations[variation.variationId].top_95}
displayPositiveSign={false}
/>
</TableCell>
<TableCell className={classes.monospace} align='right'>
{variation.isDefault ? (
'Baseline'
) : (
<MetricValueInterval
intervalName={'the absolute change between variations'}
metricParameterType={metric.parameterType}
isDifference={true}
bottomValue={latestEstimates.diffs[`${variation.variationId}_${baseVariationId}`].bottom_95}
topValue={latestEstimates.diffs[`${variation.variationId}_${baseVariationId}`].top_95}
/>
)}
</TableCell>
<TableCell className={classes.monospace} align='right'>
{variation.isDefault ? (
'Baseline'
) : (
<MetricValueInterval
intervalName={'the relative change between variations'}
metricParameterType={MetricParameterType.Conversion}
bottomValue={Analyses.ratioToDifferenceRatio(
latestEstimates.ratios[`${variation.variationId}_${baseVariationId}`].bottom_95,
)}
topValue={Analyses.ratioToDifferenceRatio(
latestEstimates.ratios[`${variation.variationId}_${baseVariationId}`].top_95,
)}
/>
)}
</TableCell>
</TableRow>
</React.Fragment>
))}
</TableBody>
</Table>
</TableContainer>
<Typography className={classes.analysisFinePrint}>
95% Credible Intervals (CIs). <strong> Experimenter-set minimum practical difference: </strong>{' '}
<MetricValue
value={metricAssignment.minDifference}
metricParameterType={metric.parameterType}
isDifference={true}
/>
.
</Typography>
{dates.length > 1 ? (
<div className={classes.metricEstimatePlots}>
<Plot
layout={{
...Visualizations.plotlyLayoutDefault,
title: isConversion
? `Conversion rate estimates by variation (%)`
: `Revenue estimates by variation (USD)`,
}}
data={plotlyDataVariationGraph}
className={classes.metricEstimatePlot}
/>
<Plot
layout={{
...Visualizations.plotlyLayoutDefault,
title: isConversion
? `Conversion rate difference estimates (percentage points)`
: `Revenue difference estimates (USD)`,
}}
data={plotlyDataDifferenceGraph}
className={classes.metricEstimatePlot}
/>
</div>
) : (
<Typography variant='body1' className={classes.noPlotMessage}>
Past values will be plotted once we have more than one day of results.
</Typography>
)}
<Typography
className={clsx(classes.dataTableHeader, classes.clickable)}
onClick={toggleIsShowObservedData}
role='button'
>
{isShowObservedData ? (
<ExpandMore className={classes.expandCollapseIcon} />
) : (
<ChevronRight className={classes.expandCollapseIcon} />
)}
"Observed" data
</Typography>
{isShowObservedData && (
<>
<TableContainer component={Paper}>
<Table className={classes.coolTable}>
<TableHead>
<TableRow>
<TableCell>Variant</TableCell>
<TableCell align='right'>Users</TableCell>
<TableCell align='right'>
{metric.parameterType === MetricParameterType.Revenue ? 'Revenue' : 'Conversions'}
</TableCell>
<TableCell align='right'>
{metric.parameterType === MetricParameterType.Revenue
? 'Average revenue per user (ARPU)'
: 'Conversion rate'}
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{experiment.variations.map((variation) => (
<React.Fragment key={variation.variationId}>
<TableRow>
<TableCell
component='th'
scope='row'
variant='head'
valign='top'
className={clsx(classes.rowHeader, classes.headerCell, classes.credibleIntervalHeader)}
>
<span className={classes.monospace}>{variation.name}</span>
</TableCell>
<TableCell className={classes.monospace} align='right'>
{latestAnalysis.participantStats[`variation_${variation.variationId}`].toLocaleString()}
</TableCell>
<TableCell className={classes.monospace} align='right'>
<MetricValue
value={
latestAnalysis.participantStats[`variation_${variation.variationId}`] *
latestEstimates.variations[variation.variationId].mean
}
metricParameterType={
metric.parameterType === MetricParameterType.Conversion
? MetricParameterType.Count
: metric.parameterType
}
/>
</TableCell>
<TableCell className={classes.monospace} align='right'>
<MetricValue
value={latestEstimates.variations[variation.variationId].mean}
metricParameterType={metric.parameterType}
/>
</TableCell>
</TableRow>
</React.Fragment>
))}
</TableBody>
</Table>
</TableContainer>
<Typography variant='caption' gutterBottom>
<Link href='https://wp.me/PCYsg-Fqg/#observed-data-uses-posterior-means' target='_blank'>
"Observed" data as produced from our model, not raw observed data.
</Link>{' '}
For illustrative purposes only.
</Typography>
</>
)}
</div>
)
}
Example #11
Source File: ModTableRow.tsx From ow-mod-manager with MIT License | 4 votes |
ModTableRow: React.FunctionComponent<Props> = ({ mod }) => {
const styles = useStyles();
const theme = useTheme();
const missingDependencyNames = useRecoilValue(missingDependencyIdsState(mod));
const isModOutdated = isOutdated(mod);
const isModBroken = isBroken(mod);
const addonMods = useRecoilValue(addonModList);
const [isAddonsExpanded, setIsAddonsExpanded] = useState(false);
const isAddon = mod.parent && !mod.localVersion;
const enabledMods = useRecoilValue(enabledModList);
const forceExpandAddons = useRecoilValue(isFiltering);
const shouldExpandAddons = forceExpandAddons || isAddonsExpanded;
const rowRef = useRef<HTMLTableRowElement>(null);
const isLoading = useRecoilValue(modIsLoadingState(mod.uniqueName));
useEffect(() => {
if (!isLoading || !rowRef.current) return;
rowRef.current.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'nearest',
});
}, [isLoading]);
const addons = useMemo(
() => addonMods.filter((addon) => addon.parent === mod.uniqueName),
[addonMods, mod.uniqueName]
);
const conflictingMods = useMemo(
() =>
mod.conflicts && mod.conflicts.length > 0
? enabledMods
.filter((enabledMod) =>
mod.conflicts?.includes(enabledMod.uniqueName)
)
.map((enabledMod) => enabledMod.name)
: [],
[enabledMods, mod.conflicts]
);
const isModConflicting = mod.isEnabled && conflictingMods.length > 0;
const handleExpandClick = () =>
setIsAddonsExpanded((isExpanded) => !isExpanded);
const getVersionColor = () => {
if (isModBroken) {
return 'default';
}
if (isModOutdated) {
return 'secondary';
}
if (isInstalled(mod)) {
return 'primary';
}
return 'default';
};
const getVersion = () => {
if (isInstalled(mod)) {
return mod.localVersion;
}
if (mod.remoteVersion) {
return mod.remoteVersion;
}
return modsText.versionNotAvailable;
};
const getClassName = () => {
let className = styles.tableRow;
if (isModBroken || isModConflicting) {
className += ` ${styles.brokenRow}`;
} else if (isLoading) {
className += ` ${styles.loading}`;
} else if (missingDependencyNames.length > 0) {
className += ` ${styles.missingDependencyRow}`;
} else if (isAddon) {
className += ` ${styles.addonRow}`;
}
return className;
};
const getModText = () => {
if (isModBroken) {
return modsText.modLoadError(mod.errors);
}
if (missingDependencyNames.length > 0) {
return modsText.missingDependencyWarning(
missingDependencyNames.join(', ')
);
}
if (isModConflicting) {
return modsText.conflictingModWarning(conflictingMods.join(', '));
}
return mod.description;
};
return (
<>
<TableRow className={getClassName()} key={mod.uniqueName} ref={rowRef}>
<TableCell className={styles.tableCell}>
<Box display="flex">
{isAddon && (
<Box
bgcolor={theme.palette.background.paper}
width="8px"
minWidth="8px"
marginRight={2}
marginLeft={1}
borderRadius="8px"
/>
)}
<Box width="100%">
<Typography variant="subtitle1">
<Box display="inline-block" mr={2}>
{mod.name}
</Box>
<Typography className={styles.modAuthor} variant="caption">
{' by '}
{mod.author}
</Typography>
<Typography variant="caption" />
</Typography>
<Box
color={
isModBroken || isModConflicting
? theme.palette.secondary.light
: theme.palette.text.secondary
}
>
<Typography className={styles.modText} variant="caption">
{getModText()}
</Typography>
</Box>
{addons.length > 0 && !forceExpandAddons && (
<ButtonBase
className={styles.addonExpander}
onClick={handleExpandClick}
>
<Box
display="flex"
alignItems="center"
borderRadius={theme.shape.borderRadius}
maxWidth="100%"
>
{shouldExpandAddons ? <ExpandLess /> : <ExpandMore />}
<Typography variant="caption" noWrap>
{addons.length}
{' addons available: '}
{addons.map((addon) => addon.name).join(', ')}
</Typography>
</Box>
</ButtonBase>
)}
</Box>
</Box>
</TableCell>
<TableCell className={styles.tableCell} align="right">
{mod.downloadCount || '-'}
</TableCell>
<TableCell className={styles.tableCell}>
<Chip
color={getVersionColor()}
label={getVersion()}
className={styles.versionChip}
/>
{!isModBroken && isModOutdated && (
<div className={styles.outdatedChip}>{modsText.outdated}</div>
)}
</TableCell>
<TableCell className={styles.tableCell}>
<ModActions mod={mod} />
</TableCell>
</TableRow>
{shouldExpandAddons &&
addons.map((addon) => (
<ModTableRow key={addon.uniqueName} mod={addon} />
))}
</>
);
}
Example #12
Source File: DomainDetails.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
DomainDetails: React.FC<Props> = (props) => {
const { domainId } = props;
const { getDomain } = useDomainApi(false);
const { user } = useAuthContext();
const [domain, setDomain] = useState<Domain>();
const classes = useStyles();
const history = useHistory();
const fetchDomain = useCallback(async () => {
try {
setDomain(undefined);
const result = await getDomain(domainId);
setDomain(result);
} catch (e) {
console.error(e);
}
}, [domainId, getDomain]);
useEffect(() => {
fetchDomain();
}, [fetchDomain]);
const webInfo = useMemo(() => {
if (!domain) {
return [];
}
const categoriesToProducts: Record<string, Set<string>> = {};
for (const service of domain.services) {
for (const product of service.products) {
const version = product.version ? ` ${product.version}` : '';
const value = product.name + version;
const name =
product.tags && product.tags.length > 0 ? product.tags[0] : 'Misc';
if (!categoriesToProducts[name]) {
categoriesToProducts[name] = new Set();
}
categoriesToProducts[name].add(value);
}
}
return Object.entries(categoriesToProducts).reduce(
(acc, [name, value]) => [
...acc,
{
label: name,
value: Array.from(value).join(', ')
}
],
[] as any
);
}, [domain]);
const overviewInfo = useMemo(() => {
if (!domain) {
return [];
}
const ret = [];
if (domain.ip) {
ret.push({
label: 'IP',
value: domain.ip
});
}
ret.push({
label: 'First Seen',
value: `${differenceInCalendarDays(
Date.now(),
parseISO(domain.createdAt)
)} days ago`
});
ret.push({
label: 'Last Seen',
value: `${differenceInCalendarDays(
Date.now(),
parseISO(domain.updatedAt)
)} days ago`
});
if (domain.country) {
ret.push({
label: 'Country',
value: domain.country
});
}
if (domain.cloudHosted) {
ret.push({
label: 'Cloud Hosted',
value: 'Yes'
});
}
ret.push({
label: 'Organization',
value: domain.organization.name
});
return ret;
}, [domain]);
const [hiddenRows, setHiddenRows] = React.useState<{
[key: string]: boolean;
}>({});
const formatBytes = (bytes: number, decimals = 2): string => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
};
const generateWebpageList = (tree: any, prefix = '') => {
return (
<List
className={`${classes.listRoot}${prefix ? ' ' + classes.nested : ''}`}
>
{Object.keys(tree).map((key) => {
const isWebpage =
'url' in tree[key] && typeof tree[key]['url'] === 'string';
if (!isWebpage) {
const newPrefix = prefix + '/' + key;
return (
<>
<ListItem
button
onClick={() => {
setHiddenRows((hiddenRows: any) => {
hiddenRows[newPrefix] =
newPrefix in hiddenRows ? !hiddenRows[newPrefix] : true;
return { ...hiddenRows };
});
}}
key={newPrefix}
>
<ListItemText primary={(prefix ? '' : '/') + key + '/'} />
{hiddenRows[newPrefix] ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse
in={!hiddenRows[newPrefix]}
timeout="auto"
unmountOnExit
>
{generateWebpageList(tree[key], newPrefix)}
</Collapse>
</>
);
}
const page = tree[key] as Webpage;
const parsed = new URL(page.url);
const split = parsed.pathname
.replace(/\/$/, '') // Remove trailing slash
.split('/');
return (
<ListItem
button
divider={true}
key={page.url}
onClick={() => window.open(page.url, '_blank')}
>
<ListItemText
primary={(prefix ? '' : '/') + split.pop()}
secondary={
page.status + ' • ' + formatBytes(page.responseSize ?? 0, 1)
}
></ListItemText>
</ListItem>
);
})}
</List>
);
};
if (!domain) {
return null;
}
const url =
(domain.services.find((service) => service.port === 443)
? 'https://'
: 'http://') + domain.name;
const { webpages = [] } = domain;
webpages.sort((a, b) => (a.url > b.url ? 1 : -1));
const webpageTree = generateWebpageTree(webpages);
const webpageList = generateWebpageList(webpageTree);
return (
<Paper classes={{ root: classes.root }}>
<div className={classes.title}>
<h4>
<Link to={`/inventory/domain/${domain.id}`}>{domain.name}</Link>
</h4>
<a href={url} target="_blank" rel="noopener noreferrer">
<LinkOffIcon />
</a>
</div>
<div className={classes.inner}>
{overviewInfo.length > 0 && (
<div className={classes.section}>
<h4 className={classes.subtitle}>Overview</h4>
<DefinitionList items={overviewInfo} />
</div>
)}
{webInfo.length > 0 && (
<div className={classes.section}>
<h4 className={classes.subtitle}>Known Products</h4>
<DefinitionList items={webInfo} />
</div>
)}
{domain.vulnerabilities.length > 0 && (
<div className={classes.section}>
<h4 className={classes.subtitle}>Vulnerabilities</h4>
<Accordion className={classes.accordionHeaderRow} disabled>
<AccordionSummary>
<Typography className={classes.accordionHeading}>
Title
</Typography>
<Typography className={classes.vulnDescription}>
Serverity
</Typography>
<Typography className={classes.vulnDescription}>
State
</Typography>
<Typography className={classes.vulnDescription}>
Created
</Typography>
</AccordionSummary>
</Accordion>
{domain.vulnerabilities.map((vuln) => (
<Accordion
className={classes.accordion}
key={vuln.id}
onClick={(event) => {
event.stopPropagation();
history.push('/inventory/vulnerability/' + vuln.id);
}}
>
<AccordionSummary>
<Typography className={classes.accordionHeading}>
{vuln.title}
</Typography>
<Typography className={classes.vulnDescription}>
{vuln.severity}
</Typography>
<Typography className={classes.vulnDescription}>
{vuln.state}
</Typography>
<Typography className={classes.vulnDescription}>
{vuln.createdAt
? `${differenceInCalendarDays(
Date.now(),
parseISO(vuln.createdAt)
)} days ago`
: ''}
</Typography>
</AccordionSummary>
</Accordion>
))}
</div>
)}
{domain.services.length > 0 && (
<div className={classes.section}>
<h4 className={classes.subtitle}>Ports</h4>
<Accordion className={classes.accordionHeaderRow} disabled>
<AccordionSummary expandIcon={<ExpandMore />}>
<Typography className={classes.accordionHeading}>
Port
</Typography>
<Typography className={classes.accordionHeading}>
Products
</Typography>
<Typography className={classes.lastSeen}>Last Seen</Typography>
</AccordionSummary>
</Accordion>
{domain.services.map((service) => {
const products = service.products
.map(
(product) =>
product.name +
(product.version ? ` ${product.version}` : '')
)
.join(', ');
return (
<Accordion className={classes.accordion} key={service.id}>
<AccordionSummary expandIcon={<ExpandMore />}>
<Typography className={classes.accordionHeading}>
{service.port}
</Typography>
<Typography className={classes.accordionHeading}>
{products}
</Typography>
<Typography className={classes.lastSeen}>
{service.lastSeen
? `${differenceInCalendarDays(
Date.now(),
parseISO(service.lastSeen)
)} days ago`
: ''}
</Typography>
</AccordionSummary>
{service.products.length > 0 && (
<AccordionDetails>
<DefinitionList
items={[
{
label: 'Products',
value: products
},
{
label: 'Banner',
value:
(user?.userType === 'globalView' ||
user?.userType === 'globalAdmin') &&
service.banner
? service.banner
: 'None'
}
]}
/>
</AccordionDetails>
)}
</Accordion>
);
})}
</div>
)}
{domain.webpages?.length > 0 && (
<div className={classes.section}>
<h4 className={classes.subtitle}>Site Map</h4>
{webpageList}
</div>
)}
</div>
</Paper>
);
}
Example #13
Source File: FilterDrawer.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
FilterDrawer: React.FC<Props> = (props) => {
const { filters, addFilter, removeFilter, facets, clearFilters } = props;
const classes = useStyles();
const filtersByColumn = useMemo(
() =>
filters.reduce(
(allFilters, nextFilter) => ({
...allFilters,
[nextFilter.field]: nextFilter.values
}),
{} as Record<string, string[]>
),
[filters]
);
const portFacet: any[] = facets['services.port']
? facets['services.port'][0].data
: [];
const fromDomainFacet: any[] = facets['fromRootDomain']
? facets['fromRootDomain'][0].data
: [];
const cveFacet: any[] = facets['vulnerabilities.cve']
? facets['vulnerabilities.cve'][0].data
: [];
const severityFacet: any[] = facets['vulnerabilities.severity']
? facets['vulnerabilities.severity'][0].data
: [];
// Always show all severities
for (const value of ['Critical', 'High', 'Medium', 'Low']) {
if (!severityFacet.find((severity) => value === severity.value))
severityFacet.push({ value, count: 0 });
}
return (
<Wrapper>
<div className={classes.header}>
<div className={classes.filter}>
<FaFilter /> <h3>Filter</h3>
</div>
{clearFilters && (
<div>
<button onClick={clearFilters}>Clear All Filters</button>
</div>
)}
</div>
<Accordion elevation={0} square>
<AccordionSummary expandIcon={<ExpandMore />}>
<div>IP(s)</div>
{filtersByColumn['ip']?.length > 0 && <FiltersApplied />}
</AccordionSummary>
<AccordionDetails classes={{ root: classes.details }}>
<TaggedArrayInput
placeholder="IP address"
values={filtersByColumn.ip ?? []}
onAddTag={(value) => addFilter('ip', value, 'any')}
onRemoveTag={(value) => removeFilter('ip', value, 'any')}
/>
</AccordionDetails>
</Accordion>
<Accordion elevation={0} square>
<AccordionSummary expandIcon={<ExpandMore />}>
<div>Domain(s)</div>
{filtersByColumn['name']?.length > 0 && <FiltersApplied />}
</AccordionSummary>
<AccordionDetails classes={{ root: classes.details }}>
<TaggedArrayInput
placeholder="Domain"
values={filtersByColumn.name ?? []}
onAddTag={(value) => addFilter('name', value, 'any')}
onRemoveTag={(value) => removeFilter('name', value, 'any')}
/>
</AccordionDetails>
</Accordion>
{fromDomainFacet.length > 0 && (
<Accordion elevation={0} square>
<AccordionSummary expandIcon={<ExpandMore />}>
<div>Root Domain(s)</div>
{filtersByColumn['fromRootDomain']?.length > 0 && (
<FiltersApplied />
)}
</AccordionSummary>
<AccordionDetails classes={{ root: classes.details }}>
<FacetFilter
options={fromDomainFacet}
selected={filtersByColumn['fromRootDomain'] ?? []}
onSelect={(value) => addFilter('fromRootDomain', value, 'any')}
onDeselect={(value) =>
removeFilter('fromRootDomain', value, 'any')
}
/>
</AccordionDetails>
</Accordion>
)}
{portFacet.length > 0 && (
<Accordion elevation={0} square>
<AccordionSummary expandIcon={<ExpandMore />}>
<div>Port(s)</div>
{filtersByColumn['services.port']?.length > 0 && <FiltersApplied />}
</AccordionSummary>
<AccordionDetails classes={{ root: classes.details }}>
<FacetFilter
options={portFacet}
selected={filtersByColumn['services.port'] ?? []}
onSelect={(value) => addFilter('services.port', value, 'any')}
onDeselect={(value) =>
removeFilter('services.port', value, 'any')
}
/>
</AccordionDetails>
</Accordion>
)}
{cveFacet.length > 0 && (
<Accordion elevation={0} square>
<AccordionSummary expandIcon={<ExpandMore />}>
<div>CVE(s)</div>
{filtersByColumn['vulnerabilities.cve']?.length > 0 && (
<FiltersApplied />
)}
</AccordionSummary>
<AccordionDetails classes={{ root: classes.details }}>
<FacetFilter
options={cveFacet}
selected={filtersByColumn['vulnerabilities.cve'] ?? []}
onSelect={(value) =>
addFilter('vulnerabilities.cve', value, 'any')
}
onDeselect={(value) =>
removeFilter('vulnerabilities.cve', value, 'any')
}
/>
</AccordionDetails>
</Accordion>
)}
{severityFacet.length > 0 && (
<Accordion elevation={0} square>
<AccordionSummary expandIcon={<ExpandMore />}>
<div>Severity</div>
{filtersByColumn['vulnerabilities.severity']?.length > 0 && (
<FiltersApplied />
)}
</AccordionSummary>
<AccordionDetails classes={{ root: classes.details }}>
<FacetFilter
options={severityFacet}
selected={filtersByColumn['vulnerabilities.severity'] ?? []}
onSelect={(value) =>
addFilter('vulnerabilities.severity', value, 'any')
}
onDeselect={(value) =>
removeFilter('vulnerabilities.severity', value, 'any')
}
/>
</AccordionDetails>
</Accordion>
)}
</Wrapper>
);
}
Example #14
Source File: FlowChartNodes.tsx From dashboard with Apache License 2.0 | 4 votes |
PropertyListItem = ({
itemKey,
itemValue,
nested,
}: {
itemKey: string
itemValue: any
nested?: boolean
}) => {
const [show, setShow] = useState(false)
const { palette } = useTheme()
const toggleShow = () => {
setShow((prev) => !prev)
}
let isObject = false
if (typeof itemValue === "object" && itemValue !== null) {
isObject = true
}
if (isObject && isEmpty(itemValue)) return null
if (
itemValue === 0 ||
itemValue === "unset" ||
itemValue === null ||
itemValue === "" ||
itemValue === {}
)
return null
return (
<div
style={{
marginLeft: nested ? "2em" : "0px",
borderLeft: `1px solid ${palette.grey[500]}`,
backgroundColor: show
? isObject
? `${palette.grey[100]}80`
: palette.grey[200]
: "inherit",
}}
>
<ListItem button onClick={toggleShow} style={{ paddingLeft: "0px" }}>
<Grid container>
<Grid item xs={6}>
<Typography noWrap>
<ListMarker color={palette.grey[500]} />
{itemKey}
</Typography>
</Grid>
<Grid item xs={6}>
<Typography noWrap>
{isObject ? (
<LightText color={palette.grey[500]}>Object</LightText>
) : (
String(itemValue)
)}
</Typography>
</Grid>
</Grid>
<ListItemSecondaryAction>
{isObject && <>({Object.keys(itemValue).length})</>}
{show ? <ExpandLess /> : <ExpandMore />}
</ListItemSecondaryAction>
</ListItem>
<Collapse in={show}>
{isObject ? (
Object.keys(itemValue).length ? (
show && <PropertyList data={itemValue} nested />
) : (
<ListItem>
<WrappedText>This Object is empty</WrappedText>
</ListItem>
)
) : (
<ListItem>
<WrappedText>
<div>
<b>key: </b>
{itemKey}
</div>
<div>
<b>value: </b>
{itemValue}
</div>
</WrappedText>
</ListItem>
)}
</Collapse>
</div>
)
}
Example #15
Source File: ServerListItemGroup.tsx From shadowsocks-electron with GNU General Public License v3.0 | 4 votes |
ServerListItemGroup: React.FC<ServerListItemGroupProps> = props => {
// const styles = useStyles();
const dispatch = useDispatch();
const enqueueSnackbar = (message: SnackbarMessage, options: Notification) => {
dispatch(enqueueSnackbarAction(message, options))
};
const { t } = useTranslation();
const {
item,
selectedServer
} = props;
const [expanded, handleChange] = useState(!!item.servers?.find(server => server.id === selectedServer));
const [ContextMenu, handleMenuOpen] = useContextMenu([
{ label: t('copy'), action: 'copy', icon: <CopyIcon fontSize="small" /> },
{ label: t('update'), action: 'update_subscription', icon: <Refresh fontSize="small" /> },
{ label: t('top'), action: 'top', icon: <VerticalAlignTopIcon fontSize="small" />},
{ label: t('move_up'), action: 'move_up', icon: <ArrowUpwardIcon fontSize="small" /> },
{ label: t('move_down'), action: 'move_down', icon: <ArrowDownwardIcon fontSize="small" /> },
{ label: t('delete'), action: 'delete', icon: <DeleteIcon fontSize="small" />}
]);
useEffect(() => {
handleChange(!!item.servers?.find(server => server.id === selectedServer));
}, [selectedServer]);
const handleRemoveButtonClick = () => {
props.onRemove?.(item.id);
};
function onContextMenuClick (action: string) {
switch (action) {
case 'copy':
clipboard.writeText(JSON.stringify(item));
break;
case 'update_subscription':
if (item.url) {
dispatch(updateSubscription(item.id, item.url, {
success: t('subscription_updated'),
error: t('failed_to_update_subscription')
}));
} else {
enqueueSnackbar(t('server_url_not_set'), { variant: 'warning' });
}
break;
case 'top':
dispatch(top(item.id));
break;
case 'move_up':
dispatch(moveUp(item.id));
break;
case 'move_down':
dispatch(moveDown(item.id));
break;
case 'delete':
handleRemoveButtonClick();
default:
break;
}
}
const onContextMenu = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
e.stopPropagation();
handleMenuOpen(e);
};
return (
<div
>
<Accordion expanded={expanded} onChange={() => handleChange(!expanded)}>
<StyledAccordionSummary
expandIcon={<ExpandMore />}
aria-controls="panel1bh-content"
onContextMenu={onContextMenu}
>
{ item.name }
</StyledAccordionSummary>
<StyledAccordionDetails>
{
item.servers.map(server => (
<ServerListItemSingle
selected={selectedServer === server.id}
moveable={false}
deleteable={false}
topable={false}
key={server.id}
{...props}
item={server}
/>
))
}
</StyledAccordionDetails>
</Accordion>
<ContextMenu onItemClick={onContextMenuClick} />
</div>
);
}
Example #16
Source File: Summary.tsx From UsTaxes with GNU Affero General Public License v3.0 | 4 votes |
F1040Summary = ({ summary }: F1040SummaryProps): ReactElement => (
<>
{(() => {
if (summary.amountOwed !== undefined && summary.amountOwed > 0) {
return (
<div>
<Typography variant="body2" color="textSecondary">
Amount Owed: <Currency value={-summary.amountOwed} />
</Typography>
</div>
)
}
if (summary.refundAmount !== undefined && summary.refundAmount > 0) {
return (
<div>
<Typography variant="body2" color="textSecondary">
Refund Amount: <Currency value={summary.refundAmount} />
</Typography>
</div>
)
}
})()}
<h3>Credits</h3>
<Grid container>
<Grid item zeroMinWidth>
<List>
{summary.credits.map((credit) => (
<BinaryStateListItem key={credit.name} active={credit.allowed}>
<Typography variant="body2" color="textPrimary">
{credit.name}
</Typography>
{(() => {
if (credit.value !== undefined) {
return (
<Typography variant="body2" color="textSecondary">
Credit: <Currency value={credit.value} />
</Typography>
)
}
return <></>
})()}
</BinaryStateListItem>
))}
</List>
</Grid>
</Grid>
{(() => {
if (summary.worksheets.length > 0) {
return (
<Grid container>
<Grid item zeroMinWidth>
<h3>Worksheets</h3>
<List>
{summary.worksheets.map((worksheet, idx) => (
<Accordion key={idx}>
<AccordionSummary expandIcon={<ExpandMore />}>
{worksheet.name}
</AccordionSummary>
<AccordionDetails>
<TableContainer>
<Table size="small">
<TableBody>
{worksheet.lines.map((line) => (
<TableRow key={line.line}>
<TableCell component="th">
Line {line.line}
</TableCell>
<TableCell>
{typeof line.value === 'number' ? (
<Currency
value={displayRound(line.value) ?? 0}
/>
) : (
line.value
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</AccordionDetails>
</Accordion>
))}
</List>
</Grid>
</Grid>
)
}
return <></>
})()}
</>
)