Susy prefix, suffix, pad wrong output with gutter-position inside - css

I don't know what is happening with the output of prefix, suffix and pad when using gutter-position: inside. For example:
$susy: (
columns: 12,
gutter-position: inside,
global-box-sizing: border-box
);
body {
#include pad(gutter());
}
The output is:
body {
padding-left: 1.6666666667%;
padding-right: 1.6666666667%;
}
And comparing with the squish mixin:
body {
margin-left: 0.8333333333%;
margin-right: 0.8333333333%;
}
The desired output is 0.8333333333% but pad still doesn't "split".
And they both together - locally should output the same value, but it doesn't:
body {
#include pad(gutter());
#include squish(gutter());
}
The output:
body {
padding-left: 1.6666666667%;
padding-right: 1.6666666667%;
margin-left: 0.8333333333%;
margin-right: 0.8333333333%;
}
What is happening?

These mixins are not meant for applying gutters (use #include gutter() or #include gutter(split) for that) — they are meant for additional margins and padding. In order to do that properly in most cases, they try maintain existing gutters in addition to any padding/margins you ask for. In your case, the gutters are being added to pad and not squish because your gutter-position is inside. You've asked for a gutter width, pad adds a gutter width, and you end up with double. If you want to use either of those mixins without having gutter widths included, use the no-gutters keyword (pad($width no-gutters)).

Related

Make two Sass mixins aware of each other: the output of one mixin should be different depending on how the other mixin was applied

I'm building a reusable, customizable component. The component has several configuration options, each option has several values to choose from.
Consider these two options for example:
content-position: left, right, top, bottom, center,
content-padding: small, medium, large.
The tricky part is that the padding must be different depending on the position. E. g. when content position is left, then the left padding must be enlarged and the right padding must be reduced.
It's pretty trivial to achieve with HTML classes:
<MyComponent class="--content-position-left --content-padding-small"/>
.MyComponent__Content {
.MyComponent.--content-padding-small & {
padding: 50px;
}
.MyComponent.--content-padding-small.--content-position-left & {
padding-left: 75px;
padding-right: 25px
}
.MyComponent.--content-padding-medium & {
padding: 100px;
}
.MyComponent.--content-padding-medium.--content-position-left & {
padding-left: 150px;
padding-right: 50px;
}
}
This works perfectly when you are OK with applying configuration via HTML.
But I want to provide a way of configuring this component with pure Sass, without applying HTML classes.
It should be possible to do something like this:
.Page__Products {
.MyComponent {
#include my-component--content-position("left");
#include my-component--content-padding("small");
}
}
How can I make the my-component--content-padding know which padding side should be larger, if any?
I see two obvious solutions:
Merge two mixins into one, and make it accept two arguments, something like:
#include my-component--content-position-and-padding("small", "left");
I don't like this approach. It gets really messy when there are multiple options depending on each other.
Instead of multiple single-purpose mixins, you end up with a single "god" mixin to control everything.
Keep mixins separate, but make each mixin accept all the information it needs:
#include my-component--content-position("left");
#include my-component--content-padding("small", "left");
This is better, but I don't like the redundancy: you have to specify "left" twice.
So I wonder if I could do the following, and the padding mixin would know how exactly to distort padding depending on which position has been applied by the other mixin:
.Page__Products {
.MyComponent {
#include my-component--content-position("left");
#include my-component--content-padding("small");
}
}
This would be trivial to do with a real programming language, but Sass seems to fall short?
PS I don't want mixins to depend on the order they have been applied in and on global variables.
constants:
$none: none;
$left: left;
$right: right;
$mediumPadding: 100px;
$mediumPaddingLeft: 100px 50px 100px 150px;
$mediumPaddingRight: 100px 150px 100px 50px;
$smallPadding: 50px;
$smallPaddingLeft: 50px 25px 50px 75px;
$smallPaddingRight: 50px 75px 50px 25px;
Mixins:
#mixin align-padding($align, $padding)
{
float: $align;
padding: $padding;
}
SCSS:
.what-you-want-to-hang-it-on
{
#include align-padding($none, $mediumpadding);
}

CSS calc() behaviour in CSS variables

I'm curious to the behaviour of using calc() in setting a CSS variable.
Example:
#test {
--halfWidth: calc(100% / 2);
}
Now, if the #test element, say a div, was 500px wide, I would like the --halfWidth variable to be set to 250px.
But, as far as I can tell the var(--halfWidth) code used elsewhere simply drops in the calc(100% / 2) string instead of 250px. Which means that I can't use the calculation of say element A and use it in element B later on, since it would simply set for example width: var(--halfWidth); as half the width of element B instead of half the width of element A, where the variable was defined.
I've scoured the web trying to find any documentation on the behaviour of this, but I have so far drawn a blank.
Ideally, setting a CSS variable using calc should be available in two variants:
One variant working just like this example, simply dropping in the string as-is, bar any in-string variable replacements.
A second variant where calc() would yield the result of the calculation instead of simply replacing the string.
How to achieve this? I'd rather leave the actual implementation to people suited to it, but one possibility would be an eval() kind of thing; like eval(calc(100% / 2)) would give the result 250px.
Anyway, if anyone have any real documentation on this behaviour or a solution to how to get the example above to yield the result instead, I'm all ears!
Edit: Just FYI, I have read the specs at https://drafts.csswg.org/css-variables/
This is kind of a tough question to answer cause the answer will not be:
Do it like this...then it will work
The problem you are facing is the normal behavior of CSS. It cascades the styles. If what you are trying to achieve would work it would get real messy after a short amount of time.
I mean how cool is it that you can define a variable like this
#test {
--halfWidth: calc(100% / 2);
}
where var(--halfWidth) should always be calc(100% / 2). Did you note that it will always be half the width of the parent element?
Imagine how strange it would be if a programmer in a few months reads your code and has box with a width of 1000px set with --halfWidth and now it is 250px wide ... I would think the internet is broken :) It should just be 500px wide.
To achieve what you want, you could/should define different vars defining the widths of the parent elements. And split it down to the children.
One approach to this is to dynamically add a line to the CSS Object Model (CSSOM) which explicitly declares the width of the .halfwidth class.
This width will then apply to all divs with the .halfwidth class.
In the example below, I have, additionally, made .element-a horizontally resizable, so that you can see more clearly that as you change the width of .element-a, the width of both .halfwidth divs changes proportionately, including the .halfwidth div which is a child of .element-b.
Working Example:
let myStylesheet = document.styleSheets[0];
const elementA = document.getElementsByClassName('element-a')[0];
let elementAWidth = window.getComputedStyle(elementA).getPropertyValue('width');
const calculateHalfWidth = (elementAWidth) => {
myStylesheet.insertRule('.halfWidth { width: ' + (parseInt(elementAWidth) / 2) + 'px; }', myStylesheet.cssRules.length);
}
calculateHalfWidth(elementAWidth);
// ================================================================================
// THE SCRIPT BELOW USES A ResizeObserver TO WATCH THE RESIZABLE .element-a DIV
// ================================================================================
const elementAObserver = new ResizeObserver(entries => {
for (let entry of entries) {
if (entry.contentRect.width !== elementAWidth) {
calculateHalfWidth(entry.contentRect.width);
}
}
});
elementAObserver.observe(elementA);
body {
font-family: sans-serif;
}
div.element {
float: left;
width: 200px;
height: 100px;
margin: 12px 3px;
text-align: center;
border: 1px solid rgb(0, 0, 0);
}
div.element h2 {
font-size: 18px;
}
div.element-a {
resize: horizontal;
overflow: auto;
}
div.element-b {
width: 300px;
}
div.halfWidth {
height: 40px;
margin: 0 auto;
border: 1px dashed rgb(255, 0, 0);
}
div.halfWidth h2 {
font-size: 14px;
}
<div class="element element-a">
<h2>Element A</h2>
<div class="halfWidth">
<h2>halfWidth</h2>
</div>
</div>
<div class="element element-b">
<h2>Element B</h2>
<div class="halfWidth">
<h2>halfWidth</h2>
</div>
</div>

susy proper way to switch grid settings in breakpoint

I am attempting to define some default behaviors for a grid and then override them at specific breakpoints. In the following example I would like the two divs to be stacked on top of each other, with slightly modified gutter settings from the default, and then at 800px and above I would like the divs to stack next to each other. The second part does not happen. Seems like some margin settings from the less than 800px scenario are being applied to the greater than 800px scenario. Please let me know how to code this and adhere to susy best practices.
HTML:
<div class="container">
<div class="primary">
<p>I am Primary</p>
</div>
<div class="secondary">
<p>I am Secondary</p>
</div>
</div>
SCSS:
$susy:
(
flow: ltr,
output: float,
math: fluid,
column-width: false,
container: 1200px,
container-position: center,
last-flow: to, columns: 12,
gutters: 1 / 4,
gutter-position: after,
global-box-sizing: border-box,
debug: (
image: hide,
color: rgba(#66f, 0.25),
spot: background, toggle: bottom right)
);
* {
#include box-sizing(border-box);
}
.container{
#include container;
}
.primary{
background-color: red;
}
.secondary{
background-color: blue;
}
// Mobile first layout with slightly different
// gutter settings from default
#include with-layout(12 0.5 split){
.primary{
#include span(12);
}
.secondary{
#include span(12);
}
}
// this layout should take over at 800px and above
// and share a row but instead boxes end up on different
// rows
#include susy-breakpoint(800px, $susy)
{
.primary{
#include span(first 6);
}
.secondary{
#include span(last 6);
}
}
I also made a codepen example that can be found here:
http://codepen.io/sbonham/pen/vLKvMJ
Yep, Susy is just writing CSS values, so you have to handle this the same way you would with plain CSS. Susy doesn't know your DOM, so it has no way of knowing that you want to override values that you set before. If we assumed you always want to override, we would have to output massively bloated code.
There are two solutions here:
Put your small-screen layout inside a max-width media-query, so it doesn't affect larger screens.
Or: override those global values inside the large-screen media-query. The problem to fix is the extra margins added by your initial split gutters.
I prefer the first solution, because I think overrides are ugly. But if you're dealing with some small browsers that doesn't support media-queries (do those still exist?), then you'll need to use the second solution. Try:
#include susy-breakpoint(max-width 800px, 12 0.5 split) {
.primary{
#include span(12);
}
.secondary{
#include span(12);
}
}
this seems like a hack, but hopefully you get something out of this! I added the following to your codepen:
.primary, .secondary {
display: inline-block;
margin: gutter(12);
width: span(12);
width:500px;
}
http://codepen.io/alexG53090/pen/wMWNzR

Susy: How to extend content box to cover grid-padding as well?

I just started to play with Susy. I have a 12 column grid that has grid-padding on it. Now i want the header of my page to span the whole grid including the grid-padding. What I'm doing right now is calculating the overall width and then setting a negative margin on the header. That's feels rather hacky to me... Is there a cleaner way to do it?
$total-columns : 12;
$column-width : 3.5em;
$gutter-width : 1.25em;
$grid-padding : 2em;
$total-width: ( $total-columns * ($column-width + $gutter-width) ) + ( 2 * $grid-padding ) - $gutter-width;
header {
height: 150px;
width: $total-width;
margin-left: -$grid-padding;
}
You have two good options. One is a simplified version of what you have. Since block elements are 100% width by default, you can simply eliminate your width setting (and all that hacky math).
header {
height: 150px;
margin: 0 0 - $grid-padding;
}
Your other option is to use multiple containers on the page. That requires a change to the markup, but sometimes it's a simplification that works well. If you move the header outside your current container, and declare it as a container of it's own, that will do the trick.
(as a side note: if you do need the full width ever, you can simply use the columns-width() function (for inner width, without padding) or container-outer-width() for the full width including the padding.)
UPDATE:
I've been using this mixin, to apply bleed anywhere I need it:
#mixin bleed($padding: $grid-padding, $sides: left right) {
#if $sides == 'all' {
margin: - $padding;
padding: $padding;
} #else {
#each $side in $sides {
margin-#{$side}: - $padding;
padding-#{$side}: $padding;
}
}
}
Some examples:
#header { #include bleed; }
#nav { #include bleed($sides: left); }
#main { #include bleed($sides: right); }
#footer { #include bleed(space(3)); }

SASS: Inheriting set variable from parent in mixin

I have created a jsFiddle to demonstrate this issue. It's just an example.
What I'm doing
Let's say I'm making a flexible grid. My HTML looks like this:
<div>
<p>a</p>
<p>b</p>
<p>c</p>
<p>d</p>
</div>
Four columns. I have two mixins and a global variable called $gutter. In my mixin, I call this variable to add gutters and change the widths.
$gutter: 1%;
#mixin col($width){
float: left;
width: $width - ($gutter * 2);
margin: 0 $gutter;
}
#mixin row(){
width: 100%;
overflow: hidden;
}
I use it like so:
div { #include row(); }
p { #include col(25%); }
What I want to do
Now let's say I want to add a second, different grid to the page. I create this HTML and give each grid and ID to differentiate them:
<div id="one">
<p>a</p>
<p>b</p>
<p>c</p>
<p>d</p>
</div>
<div id="two">
<p>a</p>
<p>b</p>
<p>c</p>
<p>d</p>
</div>​
I want the second grid to have a different gutter width. Or, alternatively, no gutters.
#one { #include row(); }
#two { #include row($gutter: 0); }
This obviously does not work. Because the number of columns can be variable, I cannot add this $gutter:0 declaration to each instance of #include col(). It breaks the DRY principle and eventually (in complicated layouts) becomes unmaintainable.
The question
How can I allow a variable set in one mixin to filter down to another (on a child element)? I am aware that I could simply do this:
#mixin row(){
width: 100%;
overflow: hidden;
.col { etc etc etc }
}
But the class name may not always be .col. Does this make sense? I want the col() mixin to inherit a variable I pass through to the row() mixin. How?
I forked the jsfiddle from Sófka and extended it with the CSS Child selector (">") to target any tags in the row.
// Inside the row mixin
& > * {
#include col($columnWidth, $gutter);
}
Furthermore I added the column width attribute to the mixin and set a default gutter.
$defaultGutter: 1% !default; // Make sure the variable is set
#mixin col($width, $gutter: $defaultGutter){
...
}
See: http://jsfiddle.net/N44LW/12/
PS.:I'm not sure if I fully understood your question, but hopefully this helps.

Resources