Make Full Page Scrollbars Control Table with Sticky Headers (CSS Only) - css

The following sandbox shows code that works perfectly well for a Material UI Table placed within a Paper component with a sticky header: https://codesandbox.io/s/2n40y
I would like the scrollbars (horizontal and vertical) to appear at the edges of the browser page, bottom and right respectively, yet still control the scrolling of the Table itself.
Currently I am only able to do this when removing the table from the Paper and making it a child of the page directly -- or a child of a main div that would span the full page height. Yet I need the Paper component to remain there, where other components will be placed above and below it.
Any ideas on how to achieve this?
UPDATE: in the attached sketch, the browser border appears in black and the scrollbars where they should ideally be appear in green. There is a container div in the middle of the page that contains the table in red. The table's headers should be sticky and the table shouldn't appear beyond the container div which acts as an aesthetic wrapper around it. Ideally, the browser vertical scrollbar would scroll the whole page down while leaving the page header (title + subtitle) and the table headers sticky. Also, when horizontally scrolling, the table should scroll within the container div. This is why I marked the parts that should not ideally appear in dashed lines.

All the changes we need to make are on demo.js
Firstly, we need to use a custom MUI TableContainer as the containerComponent for your #devexpress/dx-react-grid-material-ui Table. Basically, the purpose of this is so we can remove the overflow properties of the Table so that the scrolling is primarily for the body to address the requirement of
the scrollbars (horizontal and vertical) to appear at the edges of the
browser page, bottom and right respectively
import TableContainer from "#material-ui/core/TableContainer";
import makeStyles from "#material-ui/core/styles/makeStyles";
const useStyles = makeStyles({
tableContainer: {
overflow: "initial"
}
});
const MUITableContainer = ({ children, ...rest }) => {
const classes = useStyles();
return (
<TableContainer classes={{ root: classes.tableContainer }} {...rest}>
{children}
</TableContainer>
);
};
Secondly, at your MUITableContainer, get rid of the height: 400px so that the table's height will respond to the content. Again, the browser body bottom & right scrollbars will now control the document's scroll positions - this includes the table. Take a look at the Table containerComponent prop as well - we have assigned the custom TableContainer we created earlier.
<Paper>
<Grid rows={rows} columns={columns} rootComponent={GridRoot}>
<Table
containerComponent={MUITableContainer}
tableComponent={StickyTable}
/>
...
Lastly, to test this requirement:
there will be other objects before and after it.
Just render sample components before & after the Paper component

I cannot see the design spec you have referenced, but going off the sample in the comment on the first answer, I think you are trying to keep the header and footer from going off the left edge of the viewport when the user scrolls. But using just CSS, no JavaScript.
I took the entirety of the HTML (only the HTML) from your original code at https://codesandbox.io/s/2n40y (specifically <div id="root">) and dropped that into a Codepen.
Then I added a few visual design styles from your example so it looks kinda close.
Then I added the following CSS to make the column headers sticky:
th {
position: -webkit-sticky;
position: sticky;
top: 0;
}
I dropped in the following HTML as the first child and last child of <div id="root">, though it really doesn't matter where they live as long as they are not in the table.
<div class="stuckHeaderFooter">
[...]
</div>
To keep those from scrolling off screen when I scroll to the right, I made them sticky to the left (for RTL content you would need to make it sticky to the right):
.stuckHeaderFooter {
position: -webkit-sticky;
position: sticky;
left: 0;
padding: 0 1em;
display: inline-block;
}
The padding just makes it look less ugly. The inline-block is there to keep the block-level elements from filling the entire document width, which would keep them from being properly sticky since they would be wider than the viewport. You will probably have to set a max-width using vw units, but without knowing your content, target sizes, etc., I cannot say what would be best.
Finally, you have to remove the inline height: 400px on the first <div> under the root (<div class="MuiPaper-root MuiPaper-elevation1 MuiPaper-rounded" style="height: 400px;">). If you cannot override it because something injects it, then this style will override it for you but it brittle without knowing what else may be going on:
#root > div {
height: auto !important;
}
CSS only, no JavaScript in this approach at all, which is what I think you wanted.
Pen: https://codepen.io/aardrian/pen/PoNMopM
Debug view: https://cdpn.io/aardrian/debug/PoNMopM
Update: 5 October 2020: Looking at the sketch provided with the question, it is important to note that the container that does the clipping is the one that gets the scrollbar. So your options for a CSS-only solution are limited:
Add a fake block to make a visible border on the right (I updated the pen to add one; look at #root::after for an example but you will still need JS to make sure that does not appear until the table starts to get covered).
Yeah, I ran out of ideas.
In conclusion, I don't believe there is a CSS-only solution here because of how clipped areas work.

You can achieve this by setting the height and width to be equal to the viewport:
<Paper style={{ height: "100vh", width: "100vw" }}>
Example modified: https://codesandbox.io/s/table-sticky-header-forked-l77is?file=/demo.js

Related

What does it mean to "apply a global class name"?

I'm trying to build my interface using Material-UI (current version is 3.1.2.)
A while ago when I was using Bootstrap4, I added style="overflow-y: scroll; height: 100%" to my head tag to always show a scrollbar to stop the app from jumping around when the page content grew longer than the screen.
Now that I'm trying to use Material-UI, the overflow setting seems to cause problems when combined with Material-UI's popup components (popper, menu, etc.)
I read in the FAQ that this is a known issue with a workaround: https://material-ui.com/getting-started/faq/#why-do-the-fixed-positioned-elements-move-when-a-modal-is-opened-
But I'm not sure what that's telling me to do.
What is implied by the phrase "apply a global class" in the following?
"apply a global .mui-fixed class name ... to handle those elements."
Where do they intend that mui-fixed would be added - to every component in my app? To the anchor of the menu/popper? Or do they mean that I'm supposed to apply it "globally" (ie. high up in the React page component hierarchy somehwere?)
In the meantime, as a workaround, I've just removed the overflow styling from my html element.
The app content still jumps about when the content grows/shrinks across page size, but it seems less ugly than when I was using Bootstrap.
I tried adding the mui-fixed class to my body, so when the app starts, it looks like:
<body style="overflow-y: scroll; height: 100%" class="mui-fixed">
So when the page is short, there is a disabled scrollbar. If the page grows, the scrollbar becomes enabled.
Then, while the menu popup is displayed, Material-UI changes it to this:
<body style="overflow: hidden; height: 100%; padding-right: 17px;" class="mui-fixed">
Which looks fine, regardless of the content's length. But then when the menu is dismissed, Material-UI changes the body element to:
<body style="height: 100%; padding-right: 0px;" class="mui-fixed">
The overflow style is gone and this results in the scrollbar being removed entirely if the page content is shorter than the window - so the page content "jumps" across to fill the space from the missing scrollbar.
This is for scrollbar if I got it correct, then it would be apply "globally" one time.
in your styles.ts you can add:
'#global': {
'.mui-fixed': {
// something here
},
},
and it will apply globally!

Recharts Responsive Container does not resize correctly in flexbox

I'm trying to create a custom collapsable legend for my data visualization app. It uses react and recharts. the component renders nicely the first time. But when I collapse the legend and reopen it, the responsive container doesn't shrink to fit. This would be easier if I knew the size of the parent container in pixels but I don't have that information on render. Is this a bug with recharts or flex box or am I doing it wrong?
Heres the code: https://codesandbox.io/s/8krz9qjk52
Clarification: The problem is that when I close and then open the legend, the legend component gets pushed out of the viewing area and the chart does not shrink back to the original smaller size.
It seems rather hacky but a viable fix is to set width to 99% with a height or aspect ratio.
<ResponsiveContainer width="99%" aspect={3}>
See this issue:
https://github.com/recharts/recharts/issues/172
I know this is a really old issue, but I'm posting here just in case somebody else lands here via Google.
I don't know why this works, but setting position: absolute on .recharts-wrapper will fix this issue entirely. So far I have found no downsides to this, but YRMV.
Since I'm having similar problems in my project, I decided to stick with this question. I was finally able to get a working solution!
codesandbox
I'll list the changes I made from biggest to smallest:
I lifted state up
from CustomLegend to CustomChart.
Now, CustomChart is in charge of what happens. It passes visibility information to CustomLegend as a prop. How does CustomChart know when to change state?
CustomChart has a member function called handleButtonClick that sets its state. handleButtonClick is sent to CustomLegend as a prop. So CustomChart renders CustomLegend like this:
<CustomLegend
items={legendData}
onClick={this.handleButtonClick}
legendVisible={this.state.legendVisible}
/>
Now in CustomLegend, we can include this in the button definition: onClick={this.props.onClick} Note that this pattern only works if the original function is bound to the parent since it alters the parent's state.
chart-container is now a CSS
Grid container.
It renders an inline style handling the column width based on state.
<div
className="chart-container"
style={{
gridTemplateColumns: this.state.legendVisible
? "80% 1fr"
: "1fr min-content"
}}
>
Note that 1fr is a fractional unit which, in this case, serves to fill the remaining space.
In styles.css, grid-auto-flow is set to column which allows things to be placed in the grid as columns. The things we place into the grid are SampleChart and CustomLegend, so SampleChart is the left column and CustomLegend is the right column.
gridTemplateColumns: 80% 1fr: When the legend is visible, we want the left column to take up 80% of the width and the right column to fill the rest.
gridTemplateColumns: 1fr min-content: When the legend is invisible, we want the right column to take up the minimum amount of space which its elements fill and the left column to fill the remaining space.
CustomLegend has only a single render method.
Instead of renderVisible and renderHidden, there is one method which conditionally renders the legend-list based on props (dependent on CustomChart's state).
The legend-container always renders now (it is a grid item).
Other small changes:
In SampleChart: <ResponsiveContainer width="100%" height="100%" className={props.className}> This has been changed because <SampleChart className="chart-area" data={data} /> actually sends className as a prop. Be careful with this! You need to set className directly on the parent tag in SampleChart's returned hierarchy (in this case the ResponsiveContainer). Try going back to your original codesandbox and changing the background-color on chart-area in styles.css (nothing happens!).
overflow: scroll in all instances from styles.css because we no longer need it.
You'll notice that if you attempt a grid layout using only fr units or any flex layout on chart-container, that the chart doesn't resize properly. Unfortunately, Recharts' ResponsiveContainers just don't play nicely with Grid or Flexbox (the project's main contributors are gradually tapering off support – no commits in 3 months as of now).
try this, it resolved the responsiveness (resizing) for both width and height
<div style={{position: 'relative', width: '100%', paddingBottom: '250px'}}>
<div
style={{
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
top: 0,
}}
>
<ResponsiveContainer>
<YourChartGoesHere />
</ResponsiveContainer>
</div>
</div>
The Best bet is to give it a width of 100% then play around with the aspect ratio till if satisfys you. I did this to adapt to different screen sizes
const theme = useTheme(); //this is from mui
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
<ResponsiveContainer width="100%" aspect={isMobile?1.5:1.8}>
Works like a charm
Add the following to your styles.css:
html, body, #root {
width: 100%;
height: 100%;
}
otherwise .chart-container, even though its width and height are set to 100%, will not take up the entire viewport. Then in SampleChart.jsx, change this:
<ResponsiveContainer width="100%" height={400}>
to this:
<ResponsiveContainer width="100%" height="100%">
to make your ResponsiveContainer's height... well, responsive.

Have a visible HTML element but out of the positioning workflow

My biggest problem here is to express what I want, so please free to alter the formulation / suggestion correct wording for things.
On mobile I wish my page to be only vertically scrollable (page width and view port width are the same. A bug is causing an element adding more width than it should. I have identified the culprit element, when I set this element style to "display:none;" the display is correct (no horizontal scroll), when I don't I get an horizontal scroll.
To make it clear, with ".culpritElement {display: none}":
With culpritElement visible:
culpritElement is generated with some inline style by a third party library that I don't want to tweak. Is there a CSS directive to set to make the element visible but out of the positioning flow of the others (and page size computing).
You could set .culpritElement { max-width: 100vw; overflow-x: hidden; }
Or you could apply the above css style to its parent element

Make elements same height with Angular Material + Flex

I have an app I'm building that has a basic layout with a page header, a sidebar, and a main content area. I'm using Angular Material (1.0.5) with Angular.js (1.5.0). I'm using Angular Material's flexbox layout directives for my application's layout.
My problem is that I'm using flex to extend the sidebar to the bottom of the page, but when I scroll on a page with many ng-repeated items, it stays the height of the window. I would like it to extend all the way to the bottom of the page itself, as far as the repeated items goes. I've tried several different ways using Angular Material's flex directives, however this is the best I've been able to do thus far. One option I could do that I'd like to avoid is to calculate the height it needs to be using JavaScript in a $timeout, but like I said, that would be my last choice. Another option I know is available is to make the content area scrollable so that the header and sidebar are always fixed there. I would do that, but the client dislikes that and said no. (hence, not an option)
I've put together a CodePen to reproduce the issue I'm having. This has got my layout pretty much identical to what my app has got for its layout at the moment.
Firs create a wrapper class and insert all the content inside the wrapper
.wrapper {
width: 100%;
height: 100%;
overflow-y: auto;
}
<body ng-app="my-app" layout="column">
<div class="wrapper" layout="column">
ADD ALL YOUR CONTENT HERE
</div>
</body>
And for your rows / header not to shrink, add flex-shrink: 0 to them
.header,
.row-item {
flex-shrink: 0;
}
Result:
http://codepen.io/retamalph/pen/GZYZJo?editors=1100

How can I open popover outside scroll?

My problem is when popover comes out it always stay inside of scroll. So that when I need to see popover content I need to scroll down then I can see it. I use z index. But I can not show the popover out side of scroll. I am using angular popover.
If I use position fixed instead of absolute it always open aspect of window and I don't want it.
This is an aspect of how CSS works. You are using incompatible CSS techniques. A child element (popover) that is absolutely positioned cannot be rendered outside a parent element boundary with overflow restrictions (hidden or scroll). The overflow property tells the browser to enforce rendering restrictions on all child elements except ones with "fixed" position.
If you show your code, we can probably help you achieve your goal with some modifications.
Edit
With the example provided, all that needs to be done is to add a CSS rule to the .sectionone element for position: static
.sectionOne {
position: static; // solution
overflow-x: scroll; // in example provided
}
.table {
width:1000px; // in example provided
}

Resources