Comments (37)
has there been any progress for this? I am trying to select td's in a table table tr td:first-of-type
from aphrodite.
One hack which we're cautiously using right now works only because of the way pseudo-selectors are handled.
const styles = StyleSheet.create({
parent: {
':hover .child': {
backgroundColor: blue
}
}
});
Which is totally not part of the API, and I fully expect it to break at some point.
from aphrodite.
const styles = StyleSheet.create({ parent: { ':hover .child': { backgroundColor: blue } } });
This is actually rather intuitive. I'm using it in a project.
from aphrodite.
Another motivation for supporting child selectors (outside of pseudo-classes even) is to style React components that we don't control. For example, React Bootstrap has a NavItem component that renders <li><a /></li>
, and supports setting only the <li>
element's className.
In this case I want to write:
StyleSheet.create({
navItem: {
'selector(a)': { color: linkColor }
}
});
but sometimes would want more nested selectors like selector(a img)
too.
from aphrodite.
Interesting! That definitely was not an intention of the API design, but perhaps is something we should support for the sake of API simplicity. I don't see any immediate reason for that to break on merging.
from aphrodite.
Thinking more about the selector(a)
syntax, I'm worried about the non-determinism demonstrated in the following situation:
const Component = () => <div className={css(styles.foo)}>
<div className={css(styles.bar)}>
<span>Hello</span>
</div>
</div>
const styles = StyleSheet.create({
foo: { 'selector(span)': { color: 'red' }},
bar: { 'selector(span)': { color: 'green' }}
});
The color of the resulting span will depend on the injection order of foo
and bar
, which is a kind of non-determinism we specifically try to avoid in aphrodite.
To see why this happens, consider the following:
<div><span><em>Hello</em></span></div>
with CSS
span em { color: green; }
div em { color: red; }
In this case, "Hello" will be red. The selectors have equivalent precedence, so the latter is used. If the CSS is switched to:
div em { color: green; }
span em { color: red; }
Then "Hello" becomes green. This reversal of the declaration order mimics what would happen in aphrodite depending on whether css
is first called with styles.foo
as an argument, or first called with styles.bar
as an argument.
I don't think there's a clear implementation of the proposed selector(a)
syntax that doesn't have this problem.
from aphrodite.
In general, having parents affect the styles of their children is a pattern we want to avoid, but I can see why this particular example might occur within a single component's markup.
I'm reluctant to see patterns like this become first class citizens, but we do need some escape hatch way of writing arbitrary CSS (whether it's globally scoped or scoped to the generated selector), akin to dangerouslySetInnerHTML
, which you'd be able to use to do that.
from aphrodite.
Come to think of it, this also might break on merge
from aphrodite.
I also just ran into this case. I'll proceed with @zgotsch's workaround. Is there anything specific that we could do to make this a first-class citizen so I could rely on it and not worry about this breaking in future releases? I'm happy to contribute to the project with a little direction :-)
from aphrodite.
Actually, thinking about this more, I think that this is a scenario that would be better to avoid supporting and we should find another way to work around this issue. Here's the kind of API I'd like to see (and maybe help build):
const styles = StyleSheet.create({
parent: {
':hover': {
backgroundColor: 'white',
nonCssPropertyShouldCreateAnotherClass: {
color: 'black',
},
},
},
})
const { parentClassName, nonCssPropertyShouldCreateAnotherClassClassName } = cssWithDecendents(styles.parent)
// parentClassName === 'parent-blahblah'
// nonCssPropertyShouldCreateAnotherClassClassName === 'nonCssPropertyShouldCreateAnotherClass-blahblah'
Which would produce classes with the following CSS:
.parent-blahblah:hover {
background-color: 'white';
}
.parent-blahblah:hover .nonCssPropertyShouldCreateAnotherClass {
color: 'black';
}
Obviously, the names are up for debate. But I think an entirely new method should be used and we shouldn't overload the css
method. Also, the css
method should probably warn when given a property that is not a css property it understands (maybe? but that's a different issue).
Thoughts 💭?
from aphrodite.
@kentcdodds I really like that interface. Seems like a great balance between being flexible and still sorta following the "inline-styling way". Some random thoughts:
- We don't have a good way to distinguish whether something is a CSS property or not. Maybe we could make the key value something like
">descendantClass"
, where the>
indicates a descendant? - What would rules for recursion be? I think I would want to limit the depth that you could go to 1 level (so you would never be generating
.a .b .c
class names).
from aphrodite.
Maybe we could make the key value something like
">descendantClass"
, where the>
indicates a descendant
Seems reasonable to me 👍
As for depth, I don't see why aphrodite should concern itself with enforcing a depth ¯_(ツ)_/¯ while I agree developers should probably not go too deep on this, I don't think we'd have to put in extra effort to combat it. It'd turn into a serious code smell before it caused aphrodite trouble I think.
from aphrodite.
If there was an API for defining a ruleset you could easily determine which are properties vs a descendantClass:
const child = StyleSheet.createRuleset({
color: 'black'
}
const styles = StyleSheet.create({
parent: {
':hover': {
backgroundColor: 'white',
child: child
}
}
})
const { parent, child } = css(styles)
// parent === 'parent-blahblah'
// child === 'child-xyz'
from aphrodite.
Ok, I think that we've got two solid ideas, which should we go with?
Use >decendantClass
const styles = StyleSheet.create({
parent: {
':hover': {
backgroundColor: 'white',
'>child': {color: 'black'}
}
}
})
const { parent, child } = css(styles.parent)
// parent === 'parent-blahblah'
// child === 'child-xyz'
Have a special API for creating rule sets
const styles = StyleSheet.create({
parent: {
':hover': {
backgroundColor: 'white',
child: StyleSheet.createRuleset({color: 'black'}
}
}
})
const { parent, child } = css(styles.parent)
// parent === 'parent-blahblah'
// child === 'child-xyz'
I'm not sure which I like better. Would love to get feedback from others.
from aphrodite.
I like the simplicity (for the developer using aphrodite) of >descendentClass
. However, I'm wondering if I'm misinterpreting something:
The >
symbol is reminiscent of .foo > .bar
in true CSS, which indicates a direct descendent, whereas via the examples given above, it seems like you are proposing that this produce css which is selecting any (direct or indirect) descendent.
I think that this would be a misleading choice of api.
Am I misinterpreting?
Also, is the goal to be able to support both direct and arbitrary descendents? It seems like arbitrary descendents should cover all desired use cases since the class names are uniquely generated, whereas direct descendents would not.
from aphrodite.
Agreed. That would be misleading... I also think that I prefer the simplicity of the >
but not at the expense of it being misleading. Other thoughts of how we could make the API more intuitive and simple?
from aphrodite.
Also, I think I agree with this:
... I think an entirely new method should be used and we shouldn't overload the
css
method.
cssWithDescendents
or css.descendents
seems reasonable to me, though I'll have to think on it a bit more.
from aphrodite.
@montemishkin You are correct, the >
symbol would be a bit misleading about what it does if we allowed any descendents. Can anyone think of a better symbol to use there, or another way to indicate children?
from aphrodite.
Would we still want to have this syntax if we supported unsafeRawCss
as discussed in #30?
from aphrodite.
Is there a reason we couldn't just do:
const styles = StyleSheet.create({
parent: {
':hover': {
backgroundColor: 'white',
child: {color: 'black'}
}
}
})
const { parent, child } = css.descendents(styles.parent)
// parent === 'parent-blahblah'
// child === 'child-xyz'
I'm pretty sure that we can just say that if the value of a css property is an object, consider it a descendant.
As for the unsafeRawCss
, the difference with this feature is that I can still get a className
back for child
which I can apply to a specific element. The use case with unsafeRawCss
that I see is that you can use any kind of selector you want and you don't get a className
back for it, so it covers cases where you're not in control of the markup (like with dangerouslySetInnerHTML
)
from aphrodite.
I'm pretty sure that we can just say that if the value of a css property is an object, consider it a descendant.
This wouldn't work, because we sometimes allow real objects as values for css properties, for instance with fontFamily
you can use arrays/objects, or with animationName
you can provide objects.
from aphrodite.
Just a drive-by kibbitz, but one possible syntax for "direct or indirect descendant" could be ">>descendentClass".
from aphrodite.
Okay, I played around with a couple different alternatives, and it looks like we haven't really talked about what gets done with the results of css.descendents
. Would it make sense to plug those things into a call to css()
? So after something like:
const styles = StyleSheet.create({
parent: {
':hover': {
backgroundColor: 'white',
'>>child': {color: 'black'},
},
},
red: {
color: 'red',
},
})
const { parent, child } = css.descendents(styles.parent);
Would we be doing className={child + " " + css(styles.red)}
or className={css(styles.red, child)}
? It sounds like we were talking about the first one, but the second one looks nicer.
I also played around with something that looks like
const styles = StyleSheet.create({
parent: {
':hover': {
backgroundColor: 'white',
'>>child': {color: 'black'},
},
},
red: {
color: 'red',
},
})
<div className={css(styles.parent)}>
<div className={css(styles.red, styles.parent.child)}> // <-
but I don't think that we'll actually be able to generate appropriate class names using this interface (even though it looks even nicer than the css.descendents
stuff).
from aphrodite.
Just a drive-by, but I really like the second API, Emily (the one with styles.parent.child
). I would also be happy with something like css(styles.red, styles.parent.descendent('child'))
or similar.
from aphrodite.
@zgotsch I like that too. I'm a little scared of a situation like this though:
const styles = StyleSheet.create({
child: {
color: 'red',
},
base: {
color: 'black',
':hover': {
'>>child': {
color: 'blue',
}
},
},
greenchild: {
':hover': {
'>>child': {
color: 'green',
},
},
},
});
<div className={css(styles.base, styles.greenchild)}>
<div className={css(styles.child, styles.base.child)}>
Presumably we would want to generate CSS like...
.child_XXX {
color: red;
}
.base_XXX-o_O-greenchild_XXX {
color: black;
}
.base_XXX-o_O-greenchild_XXX:hover .child_XXX {
color: green;
}
but then you might want
<div className={css(styles.base, styles.greenchild)}>
<div className={css(styles.child, styles.greenchild.child)}>
to do the same thing? But we can't make styles.base.child
and styles.greenchild.child
add the same class names, so maybe we have to duplicate the related CSS:
.base_XXX-o_O-greenchild_XXX:hover .child_XXX { // child from base
color: green;
}
.base_XXX-o_O-greenchild_XXX:hover .child_XXX { // child from greenchild
color: green;
}
Sorry for brain dumping. I'm gonna play around with this...
from aphrodite.
Is there any update regarding this issue?
I'm using .mySelector:nth-child(3) .childElement
which currently I can only set up as a global CSS.
from aphrodite.
@xymostech Continuing discussion about alternatives to the complexity introduced in #61 here.
While the goal of making class names an implementation detail and not controllable by the user, I think the complexity that introduces in #61 is overkill for trying to solve this problem.
Here's my proposed alternative: just use class names. I know this does, to some extent, defeat the niceness of reasoning about whether certain styles/classnames are in use, but I think this use case is sufficiently rare that I think it's okay.
const styles = StyleSheet.create({
parent: {
':hover': {
backgroundColor: 'white',
'.someChild': {
color: 'black',
},
},
},
});
const Component = () => <div className={css(styles.parent)}>
White background on hover
<div className='someChild'>
Black text on hover
</div>
</div>
Based on the discussion on this issue, this is what people are already doing as a hack, except that if we support this directly then the merging semantics will be correct.
If we so wished, we could generalize this to style arbitrary selectors, and potentially knock out #30 too, something like so:
const styles = StyleSheet.create({
parent: {
':hover': {
backgroundColor: 'white',
'selector(.someChild)': {
color: 'black',
},
},
'selector(a)': {
// styles for anchors in the element go here.
}
},
});
If we went the selector()
route, we could preserve correct merging semantics by requiring that the selector specified not contain any spaces or colons. We could include in the docs the reasoning for this syntax existing, with a discouraging note about why you shouldn't use them unless you absolutely have to :)
Thoughts?
from aphrodite.
@ide The syntax I'm proposing would have you do 'selector(a)': { 'selector(img)': { color: linkColor } }
for that instead. The generated CSS would do what you want, but having them separated would make the merging semantics more obvious.
from aphrodite.
@ide I am facing the same problem. I came up with a pretty hacky function to select children:
function children(selector) {
return `:active, ${selector}`;
}
/// use it like this
StyleSheet.create({
header: {
color: 'red',
[children('li a')]: {
color: 'blue',
}
},
})
Clearly, this works as expected only if the parent component cannot be active, otherwise you'd have to use another selector. I am using it until an official API is designed
from aphrodite.
@jlfwong :( Oof. I guess that probably happens with my pull request implementation, as well?
from aphrodite.
Is this kind of non-determinism already an issue with multiple styles on the same component? For example:
<div className={css(styles.s1, styles.s2)} />
// or
<div className={classNames(css(styles.s1), css(styles.s2))} />
const styles = StyleSheet.create({
s1: { color: 'red' },
s2: { color: 'green' },
});
My understanding is that Aphrodite will emit two class selectors of the same specificity, so the last one emitted wins. I guess technically it's deterministic but hard to reason about since the order of the css()
calls matter. But if this is OK, then there could be an argument that it's OK to have similar behavior for the selector(a)
syntax too.
from aphrodite.
@ide That is true, but you should always do css(styles.s1, styles.s2)
. Actually, I think we have that written up somewhere internal but I don't know if we ever say that in the repo. We might want to put that in the README as a caveat.
The point is, adding two classnames is non-determinism that you can avoid (by combining the calls to css()
). The non-determinism that @jlfwong is describing cannot be solved by anything that aphrodite does.
from aphrodite.
@misterfresh That's fine, but do recognize that it is a hack that's not specifically supported by aphrodite, and might break in future versions.
from aphrodite.
@xymostech if hey way that @misterfresh is handling child elements is a hack, did the main contributors to this project decide on what will be the recommended way to implement styles on nested children in the future? I can use this now as we only have a beta application, but my boss just gave me the nod on using Aphrodite (AWESOME library btw) and I'll need to know the recommended way to handle this in the future. Thanks.
from aphrodite.
Yeah, I want to selected all the anchors in a div (need to style component I don't control), whats the status of this? I see its closed but as far as I can tell there was no resolution?
from aphrodite.
@sontek This was closed due to the solution in #95, which is documented here: https://github.com/Khan/aphrodite#advanced-extensions
from aphrodite.
This hacky solution might help if you end up in this thread...
.parent {
--child-background-color: red
}
.parent:hover {
--child-background-color: blue
}
.child {
background-color: var(--child-background-color)
}
from aphrodite.
Related Issues (20)
- Typescript typings don't include flushToStyleTag
- Cant get height of Aphrodite element HOT 1
- Add paddingHorizontal, paddingVertical, etc
- Doing a descendant style with aphrodite
- Make object types explicitly inexact to support projects using flow's exact_by_default option
- Support array for css definition
- Garbage collection/stale styles
- About the type definition issue on StyleDeclarationMap HOT 2
- Option to only use insertRule in certain environments
- Update and expose flow? HOT 2
- Stylesheet.create does not support strict TypeScript type checking or intellisense HOT 3
- Is Aphrodite still actively maintained HOT 6
- How to load ESM from a CDN? (development without build) HOT 1
- how can i do this compatible?
- Replacing componentWillReceiveProps react lifecycle method with componentDidUpdate HOT 1
- how to prevent aphrodite from adding !important? HOT 2
- Handling multiple selectors
- how to target classname in aphrodite HOT 2
- How do I make the static code of css exist in the rendered style?
- Aphrodite support for CSP
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from aphrodite.