-
Notifications
You must be signed in to change notification settings - Fork 417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fragment
tag for composition
#244
Comments
Just to check, this route is impossible/unacceptable? const card = css`
background: #fff;
box-shadow:
0 1px 3px rgba(0, 0, 0, 0.12),
0 1px 2px rgba(0, 0, 0, 0.24);
`
const article = css`
font-size: 18px;
${card};
`
console.log(card)
// "a123"
console.log(article)
// "b123 a123"
// ☝️it's not a mixin, it just concatenates class names |
@silvenon we cannot do that because then the following will not be correct: const paragraph = css`
font-size: 14px;
`;
const article = css`
background-color: white;
.${paragraph} {
font-size: 16px;
}
`; Here the desired result was to use different styles for You can instead do following: const card = css`
background: #fff;
box-shadow:
0 1px 3px rgba(0, 0, 0, 0.12),
0 1px 2px rgba(0, 0, 0, 0.24);
`;
const article = css`
font-size: 18px;
`;
<div class={cx(card, article)} /> |
I thought that it would be possible to detect But if that's not possible, fragment API (mixins?) sounds good! Although instead of adjusting API to text editor plugins, we can adjust text editor plugins to API instead? What I mean is that it might be better to use |
It could be possible if we parse the CSS as it's constructed. But then we also have to distinguish between class names being interpolated and plain strings being interpolated. It adds complexity which isn't worth it imo. It's also not straightforward to understand.
I think the main appeal of composition in emotion is that when you compose 2 classes, you don't have to worry about the cascade, unlike just concatenating 2 class names. In emotion, it'll duplicate the styles to acheive the correct styles, which is what
That'd certainly be preferable, but I highly doubt that they'll accept such a PR to Another problem is that |
Thats really the last missing piece to switch to linaria. I have 2 projects in my mind. In one project we use css modules (with compose) and in the other project we use styled-components. And we do things like: // generic mixin for margins, especially nice when using TS
const margins = (marginProps) => `margin: ${compute(marginProps)}`;
const Wrapper = styled.div`
${margins};
background-color: green;
` If I get you correct, this should be possible with |
What if another function is exported from the library that where you can call it that allows you to detect that the intent is to compose? import { cx, css, compose } from 'linaria'
const paragraph = css`
font-size: 14px;
`;
// this composes the other class's styles
const paragraphArticle = css`
background-color: white;
${compose(paragraph)};
`;
// this selects the other class
const article = css`
background-color: white;
.${paragraph} {
font-size: 16px;
}
`; |
@ferdaber We did exactly the same previously. but it's not very useful because it's not possible to use interpolations etc. and it just becomes a fancier string interpolation. It's also not straightforward to implement. |
I think the issue with Can you elaborate why this method would make it impossible to use interpolations? I think this is an awesome library but this missing feature is something I use very, very often in |
Because we can't support dynamic interpolations in the
It's a valid argument, but it's fair to assume that if you are importing something, you need to know what it is that you're importing. It's the same in other parts of JS as well. It comes down to how you organize the code. You can keep fragments in a separate file (like people usually do for sass mixins etc.) to make it super obvious.
You should avoid doing |
Ah gotcha, I thought you were talking about any interpolation in general, because I think that With <div className={css`${myFragment}`} />
<div className={css(myFragment)} /> |
What if array destruturing was used? Normally:
Then to grab the rules for composition within another css or styled tag:
|
Will 1.4v get some solution for this issue? |
Unfortunately Emotion devs are removing that feature. The console will throw a red error at you, telling you it that the the feature will be removed in an upcoming version. |
Styles composition would be extremely useful once we introduce react-native support #236 |
We just ran in to a case where we would really benefit from this. A colleague of mine is currently trying to write a codemod for migrating ~50 separate apps (roughly 10 000 components) from Emotion 9 to Linaria, and this is a pattern thats used in a lot of them. Would it be possible to keep the current behaviour of a) overriding the b) use the same kind of "sniffing" thats implemented here to get the rules for |
@jonatansberg your case is dope! Can you post which exact syntax you want to have? Because there are various limitations while implementing this, I was thinking a lot recently about how to approach this, I want to achieve better code reusing.
And I would try implement 'sniffing' based on the prepending dot. It would allow to use css as className or interpolate it's styles into other css or styled. |
Yeah, that is pretty much what I'm after as well. I understand that this is a pattern that has a lot of caveats, and that you're most of the time better of solving this in other ways. Unfortunately since this used to work with Emotion, we need to find a way to either codemod it away or make it work with Linaria... Here is a complete example. Code like this: const shared = css`
color: peachpuff;
`;
const otherStyles = css`
${shared}
background: crimson;
`
const InlineComponent = styled.span`
text-transform: uppercase;
`
const Component = styled.div`
padding: 1rem;
${otherStyles}
${InlineComponent} {
font-weight: bold;
}
` Should preferably turn in to something thats the equivalent to this: .InlineComponent {
text-transform: uppercase;
}
.Component {
padding: 1rem;
color: peachpuff;
background: crimson;
}
.Component .InlineComponent {
font-weight: bold;
} This works just fine when you remove the |
Good, I will try to work on some PoC next week |
After trying to implement composition I understood that it is not possible in a way I thought it would be. So the approach we can take is similar to what CSS-Modules do with Since we have merge class names, rules from composed class names will override corresponding rules in base class regardless of the ordering, so const shared = css`
color: red;
`
const base = css`
${shared}
color: blue;
` will work the same as const shared = css`
color: red;
`
const base = css`
color: blue;
${shared}
` Which will result in I guess this not match the behavior from
Update : We have to have Nested interpolation const base = css`
color: blue;
.nested {
${shared}
}
` |
@Jayphen: Does this help in any way? |
The behaviour of overriding properties regardless of the interpolation order is fine — although it's not exactly equivalent to the spec CSS behaviour, it's the intended outcome when "mixing in" styles this way (at least in the examples I've seen in our projects). In our case, the interpolation isn't necessarily always at the beginning of the declaration, so if that is a limitation then the codemod would need to 'hoist' the interpolation(s?). I believe we also have cases where interpolations are nested, and there's no way to codemod that, which will still be an issue. edit: I see you updated your post @jayu — what would the syntax look like with the |
@Jayphen So with my current approach it could look like this : const shared = css`
color: peachpuff;
`;
const otherStyles = css`
composes: ${shared}
background: crimson;
`
const InlineComponent = styled.span`
text-transform: uppercase;
`
const Component = styled.div`
padding: 1rem;
composes: ${otherStyles}
${InlineComponent} {
font-weight: bold;
}
` And effectively it would act like |
I think I found a way to go with my initial approach to spread the styles... I will test the performance of both solutions. |
I opened a draft PR with PoC implementation, the allowed syntax is as below, please find the description in the PR #615 const fragment = css`
color: red;
font-size: 40px;
padding-top: ${(props) => props.height || '200px'};
`
const anotherFragment = css`
color: yellow;
${fragment};
border: 10px solid black;
`
const component styled.h1`
color: black;
${anotherFragment}
.sub-class {
${fragment}
}
` |
Are there any updates? |
I wish I could have more time to tackle that, but unfortunately, I don't. Version with overloaded The main issue in using any other tag for fragment is syntax highlighting inside the template string, which I believe is not a big blocker for implementing that feature. I did not investigate, but the available tools should support syntax highlighting for any language syntax in that or another way, so we check that first and then agree on the tag name. Besides the main issue, @kondei if you have a problem with the non-deterministic order of CSS classes in your bundle, you should try using some lint rule to sort your imports, which would guarantee that imports are sorted based on deterministic rules. |
Couldn't we have a suffix on the variable name that would prevent its transformation into a className? For example: const heading1__fragment = css`
background: red;
` |
@brvnonascimento
though I'm wondering whether webpack will tree-shake this without extra work from linaria / etc |
Looks like we almost went full circle to the original issue's suggestion 😅 |
So I have just landed here, after trying to evaluate Linaria as a potential style tooling base for a new DS i'm working on. My team and I had dearly hoped to engineer a props-based style approach much like https://styled-system.com/api. Sadly though, we'd need interpolation of the css fragment within a style block (so that we can axs component props). Its now become clear, that such an aspiration is not currently possible within a Linaria based project (or am I wrong there?) If its not possible currently, are there any plans in the roadmap to address this gap between styled-component's API and Linaria's? |
@glomotion I don't know whether or not the core library will add it in the (near) future, but if you just want to use it today, the following code will just work: export const style = (s: TemplateStringsArray, ...expr: Array<string | number | CSSProperties>): string => {
let res = "";
for (let i = 0; i < Math.max(s.length, expr.length); ++i) {
res += s[i] ?? "";
res += expr[i] ?? "";
}
return res;
};
// usage
const shared = style`
border: 1px solid blue;
`;
const someClass = css`
background: red;
${shared}
` It just turns the contents into a string and gives you IDE completion (at least in IntelliJ due to the word |
Any plans do support this? |
Since I don't actually use export const css = (strings: TemplateStringsArray): string => strings.join(""); For anything dynamic, I use CSS vars or don't put them inside css`` at all. This is like a mixin to avoid repeating myself when simple inheritance is not enough and want to compose something. |
Problem
Currently it's not possible to compose styles from multiple classnames together. Previously we had a
include
helper which would take a class name and return the unprocessed CSS text. I removed it because it wasn't strightforward to implement it with the new approach and I haven't needed to use it personally. In addition, an extra class name was generated whether you used it anywhere or not.Another usecase is that once you get into an interpolation inside the tagged template, we don't parse the nested interpolations. So it's not possible to do something like following:
styled-components
has exportscss
tag to support this. However, thecss
tag is used for class names in Linaria, so we can't do the same.Prior art
Emotion allows you to interpolate a class name which inlines its styles: https://emotion.sh/docs/composition
One problem with this approach is that you're interpolating a class name, but getting the CSS text instead. This breaks the following use case where you might want to refer to another class name:
Current solution
Currently, you could do something like this:
There are 2 issues with this approach:
styled
Proposal
We could add a new
fragment
tag to allow this use case:The
fragment
tag itself won't generate any class names, but when the result is interpolated, it will inline the CSS text along with the dynamic interpolations. It'll work very similarly to howstyled-components
handles interpolation ofcss
tagged template literals. As for parsing, it would be parsed similar to how thestyled.x
tags are parsed.Note: Initially I thought of having just
fragment
, but went forfragment.css
since it allows us to have syntax highlighting and autocomplete.Since the
fragment
tag won't generate a class name, to use it as a class, you could do something like this:You could even inline it if you don't like creating an extra variable:
Why not add it
It requires extra code, especially if we want to support interpolations and don't want to leave the CSS code for the fragment inside the JS bundle.
The text was updated successfully, but these errors were encountered: