Comments (20)
To give some more guidance -- we're unlikely to touch Halogen core to support CSS changes if they can reasonably be done without that. If the shadow DOM approach requires this then I think you will find we are more enthusiastic about another approach :)
from purescript-halogen.
I'm very much interested in a styled-components approach for Halogen. @nsaunders may also be interested in this topic, as he's recently released a new CSS library that's a considerable improvement over the contrib
one.
There have been some prior looks at this, such as:
https://github.com/paulyoung/purescript-styled-system
but there is no active work that I know of on a true styled-components system for Halogen. Almost everyone I know either writes external stylesheets (standard CSS) or uses something like Tailwind along with a generator script to stamp out all the ClassName
s for the classes they use.
from purescript-halogen.
I've not fully caught up with this discussion yet (I am interested in it though!) but there's a thread on the discourse that talks about some things people have done here also: https://discourse.purescript.org/t/css-strategies-for-purescript-apps/374 it probably doesn't add much to this, but just thought I'd mention anyway.
One thing I'm curious about, is what the perceived benefit of writing CSS in PureScript is? Is it just so "everything is in one place" / encapsulated with relevant components? The times I've needed to do CSS-in-PS for something it does feel like more of an obstacle than a benefit, since despite it being quite complicated to write, the type safety isn't doing much for us given there's no semantic benefits the way there is with normal code.
from purescript-halogen.
The times I've needed to do CSS-in-PS for something it does feel like more of an obstacle than a benefit, since despite it being quite complicated to write, the type safety isn't doing much for us
@garyb as you know CSS is rather complicated; and getting it wrong has the potential to create show-stopping defects. Personally, that's why I am looking for some type safety in this domain, though admittedly (and to your point) it isn't easy to find, certainly at a reasonable cost. For example:
- csstype, which is a tremendously popular TypeScript library, guards against e.g. setting the
width
property totrue
; but it doesn't stop me from e.g. setting thetop
property to"hello"
. - purescript-css, with which you are familiar, has a wide range of issues; for example, some which make it annoying/tedious to use, and some which even produce incorrect output.
Is it just so "everything is in one place" / encapsulated with relevant components?
Regardless of type safety, I think a lot of people (myself included) do find value in being able to package CSS together with the markup it affects. To your point, piggybacking off of PureScript's module system is an easy way to achieve that; and the benefit of "local reasoning" is too valuable to dismiss.
One thing I'm curious about, is what the perceived benefit of writing CSS in PureScript is?
Although this is a great question to ask, I think this issue might be more about when the CSS is rendered, how it is scoped, how it is added to the document, etc. So I have a slightly different question:
Why is it necessary to generate CSS at runtime?
ⓘ Update 11/29/22: I've created a number of examples of easy mistakes that "CSS-in-PS" can prevent.
from purescript-halogen.
Why is it necessary to generate CSS at runtime?
Great callout, I can definitely imagine a build- or bundle-time tool that transpiles a subset of purescript to CSS
The benefits in my mind to rendering the CSS on the fly would be purescript animations and dynamic classnames / targeting. good food for thought
from purescript-halogen.
Working (slightly hacky) example of shadow dom:
Halogen.HTML.Shadow.purs
Halogen.HTML.Shadow.purs
module Halogen.HTML.Shadow (shadow) where
import Prelude
import Data.Maybe (Maybe(..))
import Halogen.HTML.Core (ref)
import Halogen.HTML.Properties (IProp(..))
import Web.DOM (Element)
foreign import unsafeWrapInShadow :: Element -> Unit
shadow :: forall r i. IProp r i
shadow =
let
refHandler el = do
el' <- el
let _ = unsafeWrapInShadow el'
Nothing
in
IProp $ ref refHandler
Halogen.HTML.Shadow.js
Halogen.HTML.Shadow.js
export const unsafeWrapInShadow = el => {
const shadow = el.attachShadow({mode: 'open'});
const moveChildren = () => {
Array.from(el.childNodes).forEach(c => shadow.appendChild(c));
};
(new MutationObserver(moveChildren)).observe(el, {childList: true});
moveChildren();
};
App.Button.purs
App.Button.purs
module App.Button where
import Halogen.HTML as Html
import Halogen.HTML.CSS (style, stylesheet)
import Halogen.HTML.Shadow (shadow)
import App.Button.Style as Style
import CSS (CSS)
render
:: { containerStyle :: CSS
, buttonStyle :: CSS
, children :: Array Html.PlainHTML
}
-> Html.PlainHTML
renderPlain { containerStyle, buttonStyle, children } =
Html.div
[ shadow, style containerStyle ]
[ stylesheet Style.button
, Html.button
[ Html.classNames [ Style.className ]
, style buttonStyle
]
children
]
App.Button.Style.purs
App.Button.Style.purs
module App.Button.Style where
import Prelude
import CSS
import CSS.Cursor (pointer)
import CSS.Selector as Select
import CSS.Time as Time
className :: String
className = "button"
activeClassName :: String
activeClassName = "button-active"
button :: CSS
button = do
select (Select.element "button" `Select.with` Select.byClass className)
do
height $ pct 100.0
width $ pct 100.0
border solid (px 0.0) black
sym padding $ px 0.0
cursor pointer
color white
backgroundColor black
transition "background-color" (Time.ms 75.0) kwapEasing (Time.ms 0.0)
select
( Select.element "button"
`Select.with` Select.byClass className
`Select.with` pseudo "hover"
)
backgroundColor grey
select
( Select.element "button"
`Select.with` Select.byClass className
`Select.with` pseudo "active"
)
backgroundColor black
select
( Select.element "button"
`Select.with` Select.byClass activeClassName
)
backgroundColor black
from purescript-halogen.
I'm not very familiar with halogen core or vdom but after digging a bit a "synthetic DOM node that, when rendered, performs some effects" feels like it could fit into today's vdom api as a widget? unless i'm misunderstanding their role. At worst shadow dom could be an effectful prop like the example above signifying "this element's children should be wrapped in a shadow."
One consideration I didn't think of in the writeup is that the shadow dom approach is much less stateful, much less effectful than randomly assigning and generating classnames; "synthetic sandboxing DOM node" vs "stateful and effectful central style manager"
The styled-component approach would imply some complexity:
- styles need to be dispatched to some central style manager
- style manager needs to randomly (or maybe deterministically?) generate classnames
- style manager needs to apply styles to a stylesheet high in the dom tree
Which I'd be happy to champion, but I'd need a bit of guidance on the best way to do this with the least amount of raw JS and without littering user code with new slots / type bounds
from purescript-halogen.
Hi! There are a couple big benefits from my using purescript-css on my current project:
- it's very easy to write style expressions for reuse, replacing glob selectors or micro css classes with purescript expressions
- styles live very close to the DOM nodes they style
- very easy to parameterize styles
- potential to eliminate classnames entirely, removing a class of style bugs
Naturally the costs are non-zero:
- giving up easy access to raw CSS
- strong-type benefits are few (mainly ensuring that arguments are in the right order, vendoring, preventing typos)
- having to contend with an API that, while good, has much less horsepower behind it than the CSS spec meaning it covers way fewer usecases
from purescript-halogen.
style props
Con: would be a nightmare to use without the style-src 'unsafe-inline'
CSP value. I'd recommend against any inline styles for this reason as our apps should be secure and simple (nonces aren't simple).
from purescript-halogen.
style props
Con: would be a nightmare to use without the
style-src 'unsafe-inline'
CSP value. I'd recommend against any inline styles for this reason as our apps should be secure and simple (nonces aren't simple).
Not necessarily, I've used purescript-css in a few projects without unsafe-inline. Valuable input though, it's true there are definitely even more pits to fall in than I mentioned
from purescript-halogen.
The benefits in my mind to rendering the CSS on the fly would be purescript animations and dynamic classnames / targeting. good food for thought
- Can you elaborate a bit regarding "purescript animations"?
- Does "dynamic classnames / targeting" boil down to scoping?
from purescript-halogen.
Can you elaborate a bit regarding "purescript animations"?
By that I meant complex animations or those that are non-animate
able CSS properties
e.g. if I wanted a swirling pure CSS gradient I have to reach for PS/JS
Does "dynamic classnames / targeting" boil down to scoping?
I think so? The more i think about this the less a usecase it seems to be.
from purescript-halogen.
@cakekindel you might be interested in this article. I wouldn't encourage you to resort to CSS Modules, but I would bet that it's possible to extract at build time CSS that is written in PureScript and colocated with the Halogen component it affects. As an example, I've had some luck using webpack-virtual-modules for this purpose (though not specifically with PureScript).
from purescript-halogen.
I love the idea of deferring styles to build time, my experience definitely agrees with the article you shared.
The problem left to solve for me that css-in-purescript solves is synchronizing "stringly" selectors with the DOM, which is a much different (maybe smaller?) problem to solve than getting a CSS-in-purs DSL right.
Definitely dreaming here, but it would be neat to have build-time help like:
warning: classname not found in stylesheets
--> src/Components/Foobar.purs:82:6
82 | HH.div [ className $ ClassName "foobat" ] ...
| ^^^^^^^^^^^^^^^^^^
found similar classnames:
1) static/styles/common.css:32:1
32 | .foobar {
33 | height: 100px;
2) static/styles/common.css:48:1
48 | .golbat {
49 | display: flex;
from purescript-halogen.
Separately from this, it would be really nice (speaking as a halogen user) if there was a sort of "paved road" or suggested styling strategy, even if it's just "use parcel and sass lol," I feel like having a sane starting point would help make adopting halogen even smoother.
Definitely interested in hearing maintainers' thoughts on this, though
from purescript-halogen.
I love the idea of deferring styles to build time, my experience definitely agrees with the article you shared.
The problem left to solve for me that css-in-purescript solves is synchronizing "stringly" selectors with the DOM, which is a much different (maybe smaller?) problem to solve than getting a CSS-in-purs DSL right.
Definitely dreaming here, but it would be neat to have build-time help like:
warning: classname not found in stylesheets --> src/Components/Foobar.purs:82:6 82 | HH.div [ className $ ClassName "foobat" ] ... | ^^^^^^^^^^^^^^^^^^ found similar classnames: 1) static/styles/common.css:32:1 32 | .foobar { 33 | height: 100px; 2) static/styles/common.css:48:1 48 | .golbat { 49 | display: flex;
@cakekindel this pattern isn't exactly what you're looking for, but I wonder if it might be close enough?
-- Class names
card = ClassName "card" :: ClassName -- annotation required to avoid a compiler warning
cardTitle = ClassName "card__title" :: ClassName
cardImage = ClassName "card__image" :: ClassName
unClassName :: ClassName -> String
unClassName (ClassName x) = x
-- Style sheet - use this value at build time to render the style sheet
css :: CSS
css = do
universal &. unClassName card ? Rule.do
borderWidth := px 1
borderColor := rgb 225 225 225
universal &. unClassName cardTitle ? Rule.do
fontSize := px 24
universal &. unClassName cardImage ? Rule.do
float := left
-- Widget (or component)
cardWidget :: forall p i. Title -> Image -> HH.HTML p i
cardWidget title image =
HH.div
[ HP.class_ card ]
[ {- ... -} ]
The unClassName
utility is a little unfortunate, of course, but I guess that's the price we pay for not having a canonical data type to represent a class name...
ⓘ Update 12/2/22: I just released a version of tecton-halogen which eliminates the need for the
unClassName
utility.
from purescript-halogen.
Separately from this, it would be really nice (speaking as a halogen user) if there was a sort of "paved road" or suggested styling strategy, even if it's just "use parcel and sass lol," I feel like having a sane starting point would help make adopting halogen even smoother.
Definitely interested in hearing maintainers' thoughts on this, though
thomashoneyman/purescript-halogen-realworld#46 may be of interest. I know it's not exactly what you're looking for, but maybe that will come in the future...
On the other hand, if you do find a solution that works well for you, you could always put your own example/starter app out there as a suggestion for others. :-)
from purescript-halogen.
Separately from this, it would be really nice (speaking as a halogen user) if there was a sort of "paved road" or suggested styling strategy, even if it's just "use parcel and sass lol," I feel like having a sane starting point would help make adopting halogen even smoother.
Definitely interested in hearing maintainers' thoughts on this, though
@cakekindel Although I'm not a Halogen maintainer, you might be interested in a starter project I've put together, which demonstrates the architecture I described above. (You could use the same approach with purescript-css instead of Tecton if you'd prefer.)
from purescript-halogen.
@cakekindel are you still interested in this issue?
If so, I think I could build a strongly-typed API along these lines:
HH.button
[ X.style $
color := rgb 200 0 0
hover ? Rule.do
color := rgb 255 0 0
]
[ {- ... -} ]
Please let me know if you'd find something like that useful.
from purescript-halogen.
Ooh I like it a lot! I would definitely find this useful.
from purescript-halogen.
Related Issues (20)
- `tellAll` function is not re-exported in Halogen module
- Enabling `StateT` with `HalogenM` HOT 1
- Export tellAll from Halogen.Query to Halogen HOT 2
- Reading Effects chapter -> Could not match type ResponseFormat String with type AffjaxDriver HOT 4
- Question: how to handle events coming from a js app? HOT 2
- Communication with JS loaded via CDN HOT 2
- Child component gets rendered outside of parent, being moved to bottom of HTML body HOT 6
- `raise` should not be a blocking operation HOT 8
- Question: body-level events HOT 3
- Order of properties matters when using `value` with `min` / `max` for `InputRange` HOT 3
- Change kind of slots to not be `Type`
- `RenderSpec` doc comment still mentions `h` parameter
- Doc: the examples of "An Aff Example: HTTP Requests" give TypesDoNotUnify Error on Halogen v7 HOT 1
- Select Multiple Selected does not work HOT 4
- A bug? Weird behavior of text input fields. HOT 4
- Child component is destroyed while parent component handles output from that child HOT 5
- Array state updates HOT 4
- open and showModal are missing for dialog HOT 6
- The Component type should have role annotations
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 purescript-halogen.