feat: add FillSpace viewport-height spacer with repeating measureElement
Adds a reusable FillSpace component to dashgui that fills the remaining viewport height between an aboveRef and optional belowRef, with a scrollable middle section. Supports an optional measureElement that is repeated top-to-bottom to fill the leftover space below children (rows are rendered first, units fill height - contentHeight). Includes mock page, docs wiring, and screenshot references.
This commit is contained in:
@@ -8,6 +8,7 @@ import TilesMockPage from './mock-pages/TilesMockPage';
|
||||
import PillsMockPage from './mock-pages/PillsMockPage';
|
||||
import TextFieldsMockPage from './mock-pages/TextFieldsMockPage';
|
||||
import ListsMockPage from './mock-pages/ListsMockPage';
|
||||
import FillSpaceMockPage from './mock-pages/FillSpaceMockPage';
|
||||
|
||||
const PAGES = [
|
||||
{ id: 'panels', label: 'Panels', node: <PanelsMockPage /> },
|
||||
@@ -15,6 +16,7 @@ const PAGES = [
|
||||
{ id: 'tiles', label: 'Tiles', node: <TilesMockPage /> },
|
||||
{ id: 'pills', label: 'Pills', node: <PillsMockPage /> },
|
||||
{ id: 'textfields', label: 'Text fields', node: <TextFieldsMockPage /> },
|
||||
{ id: 'fillspace', label: 'FillSpace', node: <FillSpaceMockPage /> },
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
171
src/dev/mock-pages/FillSpaceMockPage.jsx
Normal file
171
src/dev/mock-pages/FillSpaceMockPage.jsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import { Component, createRef } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import FillSpace from '../../FillSpace';
|
||||
import { ComponentDocs } from '../ComponentDocs';
|
||||
|
||||
/**
|
||||
* Mock page exercising FillSpace: real content on top, and a repeating
|
||||
* `measureElement` filling only the leftover space down to the end of the
|
||||
* viewport.
|
||||
*/
|
||||
class FillSpaceMockPage extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.ex1 = { header: createRef(), footer: createRef() };
|
||||
this.ex2 = { header: createRef(), footer: createRef() };
|
||||
this.ex3 = { header: createRef(), footer: createRef() };
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
FillSpace renders real content on top and repeats the measurement
|
||||
element until the end of the page.
|
||||
</Typography>
|
||||
|
||||
{/* Main example: content + repeating fill to end of page */}
|
||||
<Paper variant="outlined" sx={{ p: 2 }}>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
Content + repeating element to end of page
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Box
|
||||
ref={this.ex1.header}
|
||||
sx={{ bgcolor: '#e2e8f0', p: 1, borderRadius: 1 }}
|
||||
>
|
||||
<Typography variant="caption">Header (measured)</Typography>
|
||||
</Box>
|
||||
|
||||
<FillSpace
|
||||
aboveRef={this.ex1.header}
|
||||
belowRef={this.ex1.footer}
|
||||
measureElement={
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: '#dbeafe',
|
||||
p: 2,
|
||||
borderRadius: 1,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">Einheit</Typography>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: '#fef3c7',
|
||||
p: 2,
|
||||
borderRadius: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" fontWeight={600}>
|
||||
Echte Inhalte (children)
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Diese Zeilen stehen oben, darunter wiederholt sich die
|
||||
Einheit bis zum Seitenende.
|
||||
</Typography>
|
||||
</Box>
|
||||
</FillSpace>
|
||||
|
||||
<Box
|
||||
ref={this.ex1.footer}
|
||||
sx={{ bgcolor: '#e2e8f0', p: 1, borderRadius: 1 }}
|
||||
>
|
||||
<Typography variant="caption">Footer (measured)</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Example 2: With minHeight */}
|
||||
<Paper variant="outlined" sx={{ p: 2 }}>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
With minHeight constraint
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Box
|
||||
ref={this.ex2.header}
|
||||
sx={{ bgcolor: '#e2e8f0', p: 1, borderRadius: 1 }}
|
||||
>
|
||||
<Typography variant="caption">Header</Typography>
|
||||
</Box>
|
||||
|
||||
<FillSpace
|
||||
aboveRef={this.ex2.header}
|
||||
belowRef={this.ex2.footer}
|
||||
minHeight={200}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: '#fef3c7',
|
||||
p: 2,
|
||||
borderRadius: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
Minimum height of 200px enforced.
|
||||
</Typography>
|
||||
</Box>
|
||||
</FillSpace>
|
||||
|
||||
<Box
|
||||
ref={this.ex2.footer}
|
||||
sx={{ bgcolor: '#e2e8f0', p: 1, borderRadius: 1 }}
|
||||
>
|
||||
<Typography variant="caption">Footer</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Example 3: Debug mode */}
|
||||
<Paper variant="outlined" sx={{ p: 2 }}>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
Debug mode overlay
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Box
|
||||
ref={this.ex3.header}
|
||||
sx={{ bgcolor: '#e2e8f0', p: 1, borderRadius: 1 }}
|
||||
>
|
||||
<Typography variant="caption">Header</Typography>
|
||||
</Box>
|
||||
|
||||
<FillSpace
|
||||
aboveRef={this.ex3.header}
|
||||
belowRef={this.ex3.footer}
|
||||
debug
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: '#dcfce7',
|
||||
p: 2,
|
||||
borderRadius: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
Green background shows actual spacer bounds; purple overlay
|
||||
shows full viewport coverage.
|
||||
</Typography>
|
||||
</Box>
|
||||
</FillSpace>
|
||||
|
||||
<Box
|
||||
ref={this.ex3.footer}
|
||||
sx={{ bgcolor: '#e2e8f0', p: 1, borderRadius: 1 }}
|
||||
>
|
||||
<Typography variant="caption">Footer</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
<ComponentDocs name="FillSpace" />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FillSpaceMockPage;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component } from 'react';
|
||||
import { Component, createRef } from 'react';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Box from '@mui/material/Box';
|
||||
import Chip from '@mui/material/Chip';
|
||||
@@ -15,6 +15,7 @@ import ElevatedPill, {
|
||||
ToggleElevatedPill,
|
||||
} from '../../ElevatedPill';
|
||||
import SingleLineTextEdit from '../../SingleLineTextEdit';
|
||||
import FillSpace from '../../FillSpace';
|
||||
|
||||
const MOCK_ROWS = [
|
||||
{
|
||||
@@ -292,6 +293,128 @@ class ScreenshotHarness extends Component {
|
||||
color="#e2e8f0"
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section id="fillspace" label="FillSpace">
|
||||
<FillSpaceExample />
|
||||
</Section>
|
||||
|
||||
<Section id="fillspace-measure" label="FillSpace – repeat measure element">
|
||||
<FillSpaceMeasureExample />
|
||||
</Section>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deterministic FillSpace example for the screenshot harness. Bound with a
|
||||
* `maxHeight` so the reference is stable regardless of the viewport height.
|
||||
*/
|
||||
class FillSpaceExample extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.headerRef = createRef();
|
||||
this.footerRef = createRef();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Box
|
||||
ref={this.headerRef}
|
||||
sx={{ bgcolor: '#e2e8f0', p: 1, borderRadius: 1 }}
|
||||
>
|
||||
<Typography variant="caption">Header (gemessen)</Typography>
|
||||
</Box>
|
||||
|
||||
<FillSpace
|
||||
aboveRef={this.headerRef}
|
||||
belowRef={this.footerRef}
|
||||
minHeight={80}
|
||||
maxHeight={220}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: '#dbeafe',
|
||||
p: 2,
|
||||
borderRadius: 1,
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
FillSpace füllt den Platz zwischen Header und Footer.
|
||||
</Typography>
|
||||
</Box>
|
||||
</FillSpace>
|
||||
|
||||
<Box
|
||||
ref={this.footerRef}
|
||||
sx={{ bgcolor: '#e2e8f0', p: 1, borderRadius: 1 }}
|
||||
>
|
||||
<Typography variant="caption">Footer (gemessen)</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FillSpace with a repeating measurement element plus real children on top,
|
||||
* for the screenshot harness. Bound with maxHeight so the reference is stable.
|
||||
*/
|
||||
class FillSpaceMeasureExample extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.headerRef = createRef();
|
||||
this.footerRef = createRef();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Box
|
||||
ref={this.headerRef}
|
||||
sx={{ bgcolor: '#e2e8f0', p: 1, borderRadius: 1 }}
|
||||
>
|
||||
<Typography variant="caption">Header</Typography>
|
||||
</Box>
|
||||
|
||||
<FillSpace
|
||||
aboveRef={this.headerRef}
|
||||
belowRef={this.footerRef}
|
||||
measureElement={
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: '#c7d2fe',
|
||||
p: 1,
|
||||
borderRadius: 1,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption">Einheit</Typography>
|
||||
</Box>
|
||||
}
|
||||
measureGap={8}
|
||||
minHeight={60}
|
||||
maxHeight={260}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: '#fef3c7',
|
||||
p: 1,
|
||||
borderRadius: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">Echte Inhalte</Typography>
|
||||
</Box>
|
||||
</FillSpace>
|
||||
|
||||
<Box
|
||||
ref={this.footerRef}
|
||||
sx={{ bgcolor: '#e2e8f0', p: 1, borderRadius: 1 }}
|
||||
>
|
||||
<Typography variant="caption">Footer</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user