Git Product home page Git Product logo

zaid-ajaj / feliz Goto Github PK

View Code? Open in Web Editor NEW
522.0 21.0 77.0 12.27 MB

A fresh retake of the React API in Fable and a collection of high-quality components to build React applications in F#, optimized for happiness

Home Page: https://zaid-ajaj.github.io/Feliz/

License: MIT License

F# 99.16% HTML 0.14% JavaScript 0.34% CSS 0.35%
fable react html type-safe feliz fsharp dsl elmish-applications

feliz's Introduction

Feliz Nuget Build status

A fresh retake of the React API in Fable, optimized for happiness.

Here is how it looks like:

module App

open Feliz

[<ReactComponent>]
let Counter() =
    let (count, setCount) = React.useState(0)
    Html.div [
        Html.button [
            prop.style [ style.marginRight 5 ]
            prop.onClick (fun _ -> setCount(count + 1))
            prop.text "Increment"
        ]

        Html.button [
            prop.style [ style.marginLeft 5 ]
            prop.onClick (fun _ -> setCount(count - 1))
            prop.text "Decrement"
        ]

        Html.h1 count
    ]

open Browser.Dom

let root = ReactDOM.createRoot(document.getElementById "root")
root.render(Counter())

Features

  • Consistent, lightweight formatting: no more awkward indentation using two lists for every element.
  • Discoverable attributes with no more functions, Html attributes or css properties globally available so they are easy to find.
  • Proper documentation: each attribute and CSS property
  • Full React API support: Feliz aims to support the React API for building components using hooks, context and more.
  • Fully Type-safe: no more Margin of obj but instead utilizing a plethora of overloaded functions to account for the overloaded nature of CSS attributes, covering 90%+ of the CSS styles, values and properties.
  • Included color list of most commonly used Html colors in the colors module.
  • Compatible with the current React DSL used in applications.
  • Compatible with Femto.
  • Approximately Zero bundle size increase where everything function body is erased from the generated javascript unless you actually use said function.

Quick Start

dotnet new -i Feliz.Template
dotnet new feliz -n MyProject
cd MyProject
npm install
npm start

Documentation

Feliz has extensive documentation at https://zaid-ajaj.github.io/Feliz with live examples along side code samples, check them out and if you have any question, let us know!

feliz's People

Contributors

alfonsogarciacaro avatar artemyb avatar bzuu avatar callumvass avatar cmeeren avatar dawedawe avatar dbrattli avatar dependabot[bot] avatar dzoukr avatar gastove avatar jamesrandall avatar joahim avatar kerams avatar l3m avatar linschlager avatar lordnull avatar lukaszkrzywizna avatar mangelmaxime avatar michael8bit avatar mrtz-j avatar olivercoad avatar panmona avatar samme78 avatar shmew avatar skamlet avatar sydsutton avatar twith2sugars avatar uxsoft avatar zaid-ajaj avatar zanaptak avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

feliz's Issues

Remove Guid overload of text/innerText(/children)

The Guid overload of text/innerText simply calls string on the input and otherwise works just like the string overload. I suggest the Guid overload be removed, for the following reasons:

  • The overload probably doesn't add that much convenience since it's not often you'd want to render a Guid as a string
  • There is no one correct way to convert a Guid to string; several formats may be used (upper/lowercase, enclosed in brackets or not, with/without dashes)
  • The Guid overload hides the fact that some processing takes place (and which processing that is)
  • Calling string is very simple for the user to do themselves

(On the other hand, a Guid overload for prop.key makes more sense to keep.)

Add Guid overload to prop.value (or make it completely generic)

When using a Select in Material-UI, I needed to set the childrens' value to a Guid value (the ID of the entities to be selected). I suggest either adding a Guid overload to prop.value (and possibly other relevant primitives if any are missing), or simply having a single generic 'a overload.

type attribute is valid for more than just input

Looking at the MDN HTML attribute reference, I see that the type attribute is valid not only for input, but also for e.g. button and menu. The button values are (at the time of writing) a subset of the input values, but the menu props are currently not covered.

type is also a free-text attribute for embed, source, object, and style (containing MIME types).

In any case, it might be weird calling it inputType when it's relevant for more than just input.

Related: #13 and #43

Exposing React APIs: hooks and components

Explore different idea's of easily exposing React's functionality through Feliz instead of falling back to Fable.React since these functions are at the core of React/Elmish apps and should be used in order to be able to profile and optimize for unnecessary diffs in the application tree but also an easy way to componentisation and internalizing local state as opposed to using Elmish states everywhere

1 - A dedicated React module/static type that contains functions to create hooks and components (I am in favor of this one!)

open Feliz

let counter = React.element "Counter" <| fun (props: {| count: int |} ->
  let (count, setCount) = React.useState props.count
  Html.div [
    Html.h1 count
    Html.button [ 
      prop.onClick (fun _ -> setCount(count + 1))
      prop.text "Increment"
    ]
  ]

let render state dispatch = 
  Html.div [
    counter {| count = state.Count |}
    counter {| count = state.Count * 2 |}
  ]
  

Here React.useState is a hook and React.element is used to create a function component, similar (and might be an alias to) FunctionComponent.Of

The use of element instead of component because of how overloaded the term "component" is but the same be said for element as well. Any suggestions on this one?

2 - Introduce a Feliz.React namespace where these functions are globally available

open Feliz
open Feliz.React

let counter = element "Counter" <| fun (props: {| count: int |} ->
  let (count, setCount) = useState props.count
  Html.div [
    Html.h1 count
    Html.button [ 
      prop.onClick (fun _ -> setCount(count + 1))
      prop.text "Increment"
    ]
  ]

let render state dispatch = 
  Html.div [
    counter {| count = state.Count |}
    counter {| count = state.Count * 2 |}
  ]

Make Reacts function available by only opening Feliz:

open Feliz

let counter = element "Counter" <| fun (props: {| count: int |} ->
  let (count, setCount) = useState props.count
  Html.div [
    Html.h1 count
    Html.button [ 
      prop.onClick (fun _ -> setCount(count + 1))
      prop.text "Increment"
    ]
  ]

Let me hear your thoughts please! @cmeeren @zanaptak @vbfox @MangelMaxime @alfonsogarciacaro

Rewrite to component-specific props?

I was looking through the MDN HTML attribute reference and noticed that most of the attributes are actually only valid for a very limited set of elements.

How would you feel about having element-specific types/modules, ร  la Feliz.MaterialUI? I'm not convinced it's the best solution for Feliz; I just want to put it out there.

Complete API implementation for hooks from React

Complete API implementation for hooks from React, see API Reference

Basic Hooks

  • React.useState
  • React.useEffect
  • React.useContext

Additional Hooks

  • React.useReducer
  • React.useCallback
  • React.useMemo
  • React.useRef
  • React.useLayoutEffect
  • React.useDebugValue
  • React.useImperativeHandle

Suggestions/ideas/PRs with the implementation of these is very much welcome and highly appreciated ๐Ÿ™

Warning: Each child in a list should have a unique "key" prop.

It seems that each level of nesting is always producing a Warning: Each child in a list should have a unique "key" prop. in the browser console.

In old DSL I believe this only happened with ofList helper.

Workaround is to put prop.key on every element.

Repro:

let nestedDivs (model:Model) dispatch =
    Html.div [
        prop.id "id"
        prop.className "class"
        prop.children [
            Html.text "text"
            Html.div [
                prop.id "id"
                prop.className "class"
                prop.children [
                    Html.text "text"
                    Html.div [
                        prop.id "id"
                        prop.className "class"
                        prop.children [
                            Html.text "text"
                            Html.div [
                                prop.id "id"
                                prop.className "class"
                                prop.children [
                                    Html.text "text"
                                    Html.div []
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        ]
    ]

Produces:

Warning: Each child in a list should have a unique "key" prop. See https://fb.me/react-warning-keys for more information.
    in div (created by Elmish_React_Components_LazyView)
    in Elmish_React_Components_LazyView react-dom.development.js:506:32
Warning: Each child in a list should have a unique "key" prop. See https://fb.me/react-warning-keys for more information.
    in div (created by Elmish_React_Components_LazyView)
    in div (created by Elmish_React_Components_LazyView)
    in Elmish_React_Components_LazyView react-dom.development.js:506:32
Warning: Each child in a list should have a unique "key" prop. See https://fb.me/react-warning-keys for more information.
    in div (created by Elmish_React_Components_LazyView)
    in div (created by Elmish_React_Components_LazyView)
    in div (created by Elmish_React_Components_LazyView)
    in Elmish_React_Components_LazyView react-dom.development.js:506:32
Warning: Each child in a list should have a unique "key" prop. See https://fb.me/react-warning-keys for more information.
    in div (created by Elmish_React_Components_LazyView)
    in div (created by Elmish_React_Components_LazyView)
    in div (created by Elmish_React_Components_LazyView)
    in div (created by Elmish_React_Components_LazyView)
    in Elmish_React_Components_LazyView

3rd party guide

This looks fantastic IMHO.

Is this simple to extend for 3rd party stuff, such as Material-UI?

It would be great to have some kind of guide on how to do that. :)

Replace the term attr by prop

On twitter, I mentioned that I would prefer using prop instead of attr so I would like to continue the discussion here.

My point is that Feliz seems to come closer to the react philosophy where everything is a component and everything accepts properties. I don't know if that voluntary or not.

The original React DSL, is based on JSX (and so HTML) syntax which is a layer on top of react. In JSX, we are passing attributes/properties and have a separate way to pass children.

While in fact for react children are passed using children property. So for react, everything is a property.

I think that using prop instead of attr will favorize the adoption of Feliz. The other benefit is that if someone uses Feliz syntax for writing/binding components the term prop is correct in that case too.

And because HTML element (div, span, etc.) should be considered the lowest-level components possible for me it makes sense to use prop term.

IHMO asking the user to create an alias in each of their module/projects is not a good alternative because they will be thinking in term of prop while the documentation could mention attr. Or for newcomers, it's not really friendly to use module alias, etc.

What do you think?

Add support for fragment

I notice that fragment is not supported. Would be great to have, but I'm not sure exactly which syntax to use (or where to put it) since it's not a normal HTML DOM element, but a React-specific component.

Component-specific props and using generator for core Feliz components/props

I know you have said before that for the native DOM surfaced in Feliz, it makes sense to have all props be accessible in props. But I have been looking at the MDN HTML Attribute reference, and it seems that actually most attributes are specific to one or a handful of HTML elements.

I would therefore like to propose a similar solution as with Feliz.MaterialUI: Use component-specific props. The usage would be like this:

Html.form [
  form.accept "foo"  // only valid on form/input
  form.action "https://example.com/submit"  // only valid on form
  form.autocapitalize.sentences  // inherited, allowed on any HTML element
  form.children [
    // ...
  ]
]

In the example above, I make use of accept (only valid on form and input) and action (only valid on form). The autocapitalize prop is global (allowed for any HTML element, even unknown). I don't know whether children is valid for all HTML elements (e.g. maybe not input?), so it's added directly to form (if this is wrong, children should be global and inherited everywhere, making the usage identical anyway).

In order to make this possible to maintain, Feliz.Generator can be used. To play around with an example, see Feliz.MaterialUI's generate-native-dom branch, in particular the project Feliz.Generator.CoreDom which I just created to demo this. Specifically, Program.fs contains the component/prop API definition (with some helpers to indicate how common logic can be factored out), and OUTPUT_PROPS.fs and OUTPUT_COMPONENTS.fs contains the generator output.

Some benefits of using Feliz.Generator:

  • It's easy to "inherit"/duplicate props as shown above, which again makes it easier for users to only use valid props
  • If we need to change how Feliz components/props are implemented, that can be done once (in Feliz.Generator), and the API definition is still the same. No need to manually update hundreds of props/components if needing to change implementation details. This has helped me greatly in Feliz.MateriaUI so far while experimenting with different ways of doing inheritance etc., which would be next to impossible without it (imagine if I had manually rewritten thousands of lines of props only to then find out that F# type aliasing stops working properly if there are too many aliases).
  • A library with the components can be published separately, or at least referred to directly in 3rd party projects, so that it's less maintenance to include core Feliz props in e.g. Feliz.MaterialUI components.
  • Automatic handling of [<Erase>] based on whether all members are inline
  • Consistent formatting of output (for those who care about that)

Note: I have no idea how large the output files will be or which impacts it will have on compilation time. Feliz.MaterialUI currently has 7500 lines of props and 1000 lines of components, and I haven't noticed any slowdowns during Fable compilation. But it may of course be that the number of props, when "inherited"/dupliated, becomes too large to be feasible. For example, the MDN page I looked at does not list any event handlers, where AFAIK most are defined for any HTML element. So that'll be quite a bit of duplication.

What do you think? Feel free to disagree, of course. I'm completely open to this being a bad idea. Personally I'm fine with using prop and simply having to RTFM to know which props are valid where. In most common cases I think devs will know it already anyway,

prop.children suppresses development-time warning about missing keys

Is the use of React.Children.toArray necessary in prop.children?

static member inline children (elems: Fable.React.ReactElement seq) = Interop.mkAttr "children" (Interop.reactApi.Children.toArray elems)

It seems to work fine without. The only difference is that toArray seems to add a key to each child element, so you don't get the warning that arrays are supposed to have keys, which may be a bad thing if you're supposed to get that warning.

Looking at the docs for Children.toArray, its use-case seem to be flattening more complex structures, and adding keys is a side-effect of this. So it doesn't seem to be useful for this context.

I'm all very new to this low-level stuff though and may be wrong.

prop.inputType -> prop.input.type for better discoverability?

It seems that Feliz places component-specific props in separate types. For example, prop.inputType.xx. Could these be more discoverable if inputType was separated into element + prop, so that it was prop.input.type.xx? Seems to be a bit more clean. (Material-UI have a lot of component-specific props and would require a lot of these.)

(You'd have to name it type' or similar for this specific example, of course.)

Make onCheckedChange and onTextChange overloads of onChanged

Kinda similar to #14 (very happy you did that, thanks!).

I propose that onCheckedChange and onTextChange become overloads of onChanged. As a developer, I instinctively reach for onChanged when I want that behavior, because it's a well-known concept. I can then happily discover that it has simplified convenience overloads for inputs and checkboxes. Contrast this to the current names, which I'd have to discover and read about separately. Consolidating them under a well-known (and semantically correct) name improves discoverability, reduces mental load (less terms to remember) and IMHO also improves readability in this case (which variant is used isn't important enough to warrant separate names; they all do the same general thing and the compiler will complain if something's wrong.)

(Also, the current names should probably both end with d.)

Incorrect use of "keyValueList CaseRules.LowerFirst"?

AFAIK keyValueList CaseRules.LowerFirst is only for transforming a DU list into an object. Since IReactProperty and IStyleAttribute are just string * obj in disguise, createObj is probably the right choice in Feliz.

Please tell me if this is not right, because then I have misunderstood something.

Missing prop.selected

I can't find the equivalent of Fable.React.Props.HtmlAttr.Selected (should probably be prop.selected with bool value).

Naming the css units class?

I am not what to call the helper class that contains the different CSS units like px and rem. I have now called it length, implemented like this:

[<Erase>]
type length =
    static member inline px(value: int) : ICssUnit = unbox ((unbox<string>value) + "px")
    static member inline px(value: double) : ICssUnit = unbox ((unbox<string>value) + "px")
    static member inline cm(value: int) : ICssUnit = unbox ((unbox<string>value) + "cm")
    static member inline cm(value: double) : ICssUnit = unbox ((unbox<string>value) + "cm")
    /// etc. etc.

and you can use it like this:

style.margin (length.px 20)

but I am not sure if length is a good name for it, what do you think @MangelMaxime?

Add seq<ReactElement> overload to Html.div?

I notice that div can take a single ReactElement, but not multiple. Would it make sense to add such an overload? I personally don't have a problem writing prop.children and indenting one level further, but since it already has ReactElement, it might make sense to also have seq<ReactElement>.

Enable prop inheritance by using instance members

As discussed in Shmew/Feliz.MaterialUI#20, it would be nice to be able to inherit props.

I think the required changes are as follows:

  • Change this to type htmlProps () (or another name)

    type prop =

  • Change all htmlProps static members to instance members

  • Add a value let prop = htmlProps ()

  • Remove this module definition

    module prop =

  • Optionally rename the "enum" prop types contained in that module, e.g. roleProp

    type role =

  • Make the enum prop types to normal classes, e.g. roleProp (), and change all static members to instance members

  • Move those enum prop types above the main prop type (htmlProps)

  • Add members to htmlProps corresponding to those types, set to an instance of the types the modules were converted to (remember to add the corresponding documentation). For example:

    /// https://www.w3.org/WAI/PF/aria-1.1/roles
    member _.role = roleProp ()

    (This may be optimized by instantiating them in the constructor instead of on every access.)

I have no idea whether any of this impacts bundle size.

Oh, and optionally also add a type PropNotSupported = private PropNotSupported that can be used by 3rd party libraries to shadow/disallow a property that should not be inherited. (It's no problem defining this in 3rd party libraries where needed, but it might also be nice to have a single, idiomatic type for it. I don't have a very strong opinion on this.)

Reduce bundle size increase to zero :)

By looking at the source code Feliz is using class with a constructor.

module Test

type IBorderStyle = interface end

type borderStyle() =
    static member inline dotted : IBorderStyle = unbox "dotted"

which generates:

import { type } from "fable-library/Reflection.js";
import { declare } from "fable-library/Types.js";
export const borderStyle = declare(function Test_borderStyle() {});
export function borderStyle$reflection() {
  return type("Test.borderStyle");
}
export function borderStyle$$$$002Ector() {
  return this instanceof borderStyle ? borderStyle.call(this) : new borderStyle();
}

In order to minimize the bundle size, we can remove the constructor.

module Test

type IBorderStyle = interface end

type borderStyle =
    static member inline dotted : IBorderStyle = unbox "dotted"

this generates:

import { type } from "fable-library/Reflection.js";
import { declare } from "fable-library/Types.js";
export const borderStyle = declare(function Test_borderStyle() {});
export function borderStyle$reflection() {
  return type("Test.borderStyle");
}
export const x = "dotted";

We can even go further and have no bundle size increase by using Erase attribute:

module Test

open Fable.Core

type IBorderStyle = interface end

[<Erase>]
type borderStyle =
    static member inline dotted : IBorderStyle = unbox "dotted"

generates ... nothing ๐Ÿ˜œ ๐ŸŽ‰

Unable to use functions with multiple arguments

Material-UI contains many onChange props where the handler accepts two arguments. However, these functions are never invoked.

For example, a prop definition may look like this:

static member inline onChange(handler: Event -> bool -> unit) = Interop.mkAttr "onChange" handler

If I remove bool from the signature, it works, but of course then the handler doesn't get access to that value.

This behaviour seems consistent across all multi-argument functions I've tried. And it works fine when using Fable.MaterialUI, which has a DU prop | OnChange of (obj->bool->unit).

I'm a bit stuck debugging this. Do you have any idea why this could be?

CSS Properties missing some property values

Some CSS properties like:

  • margin
  • height
  • box-shadow

are missing things like "auto", "inherit", and "none" respectively. At least from what I've found so far, it's also entirely possible I'm missing something. I noticed with margin I could do:

style.margin length.auto

Though I'm not sure if that is the same result. From what I've read it sounds like you can only specify length.em once and the others must be int, does the length propagate for them? I didn't appear to do so when I looked at the source. I couldn't find anything for the other two cases I've come across so far and ended up just doing:

style.custom("height", "inherit")
style.custom("box-shadow", "none")

I've also noticed things like BorderBottom don't exist, but I'm not sure how I can modify only the bottom value given the overloads we have now.

By the way, I really love what you've done so far! It's a pleasure to use.

Feliz.Markdown and comment blocks

If you use the * operator in a partial function like

[0..3] |> List.map ((*) 5)

It causes the rest of the file to be washed out due to it thinking it's a comment.

An example here

Fragment only accept key properties

Because a fragment doesn't have a "real" existence in the DOM it only accepts key as property.

The current implementation of Feliz allows the user to provide any "HTML" props to it.

How to spread existing object props along with Feliz props?

I'm using/looking at things like downshift and downshift-hooks which provide props that you're supposed to spread, for example:

<label {...getLabelProps()}>Enter a fruit</label>

where getLabelProps is provided by the component's render function (downshift) or hook (downshift-hooks), and of course should be able to be combined with other custom props.

I'm also needing the same functionality for the Material-UI styling solution, which uses at least one mixin object in the theme (theme.mixins.toolbar) that you can combine with your own styles for a given class.

My current solution is to use this function to convert a JS object into key-value pairs:

[<Emit("Object.entries($0)")>]
/// Transforms a plain JS object to key-value pairs.
let objectEntries (x: 'a) : (string * obj) [] = jsNative

Then I have the following function which simply unboxes that to the needed type:

type prop =

  /// Converts the items in the object to an array of IReactProperty so they can
  /// be used alongside other props. Use yield! to combine these in a list with
  /// other props.
  static member spread(value: obj) : IReactProperty [] =
    objectEntries value |> unbox

(style.spread would be identical, except returning IStyleAttribute [].)

This allows usage like:

Html.div [
  yield! prop.spread (getLabelProps())
  yield prop.className "foo"
]

My question now is two-fold:

  1. Are there better ways to accomplish this?
  2. Since this is completely general and might be needed in many situations, would you consider adding support for this in Feliz?

Consolidate and expand children prop

AFAIK React's children prop accepts a "node", which can be a string, number, component, or an array of any of these. (A list of these also seems to work, based on cursory experimentation.)

I therefore suggest the following:

  • Rename prop.text and prop.innerText to prop.children, by the same reasoning as in #14/#15
  • Add a string seq overload to children, which can be useful as an effective concatenation technique, as far as I've understood the React reconciliation mechanism. (Strictly speaking you could add int seq, float seq etc. but I'm unsure how useful they are, so perhaps don't add them before someone actually needs them)

Make classList and classes overloads of className

AFAIK className is a space-separated string of class names to apply to the component/element. Why not make classList (and classWhen) and classes overloads of className? That would improve discoverability, remove confusing extra names for what is essentially the same property, and be more in line with the goal of this project as I understand it.

Unhelpful error message

I started getting this error, the message isn't too entirely helpful:

FSC : error FS0193: The module/namespace 'React' from compilation unit 'Feliz' did not contain the val 'ValLinkagePartialKey(toArray)' [C:\...\Client.fsproj]
Fsc: FSC(0,0): error FS0193: The module/namespace 'React' from compilation unit 'Feliz' did not contain the val 'ValLinkagePartialKey(toArray)'

I'll update when I've found what's causing it.

styles and sublists

Thanks for this, I'm trying it out immediately!

As it happens I was also working on a new DSL almost just like this. Wasn't a fan of the traditional double-list parameter style even after many months of giving it a chance. Rather than compete I'll choose to be happy you saved me a bunch of work. ๐Ÿ˜ƒ

Not sure how you feel about alternative suggestions/ideas since it's your library, but throwing these out as the paths I was going down, for you to consider or ignore as you please.

One:

For styles I was preferring to have the valid options "attached", and not the somewhat verbose method of having separate option types where you repeat the css property. Also unit alternatives attached instead of extra parenthesized args.

style.display.flex
style.justifyContent.center
style.margin.px 20
style.padding.rem 1.5

Doesn't cover all the overloads but gets the most important primitives which I think is 80% of the value of the feature.

Example implementation (just returning strings for example purposes):

module style =

  module Util =
    let cssDecl a b = a + ":" + b + ";"
    // SRTP to allow any numeric for units
    let inline rem< ^a when ^a : ( static member get_One : unit -> ^a ) > ( value : ^a ) = string value + "rem"
    let inline px< ^a when ^a : ( static member get_One : unit -> ^a ) > ( value : ^a ) = string value + "px"
    let inline percent< ^a when ^a : ( static member get_One : unit -> ^a ) > ( value : ^a ) = string value + "%"
    // and so on...

  module display =
    let _val s = Util.cssDecl "display" s // freeform for unrepresented combinations
    let none = Util.cssDecl "display" "none"
    let block = Util.cssDecl "display" "block"
    let flex = Util.cssDecl "display" "flex"
    let inline_ = Util.cssDecl "display" "inline"
    // and so on...

  module margin =
    let _val s = Util.cssDecl "margin" s // freeform for unrepresented combinations
    let auto = Util.cssDecl "margin" "auto"
    let zero = Util.cssDecl "margin" "0"
    let inline rem v = Util.rem v |> Util.cssDecl "margin"
    let inline px v = Util.px v |> Util.cssDecl "margin"
    let inline percent v = Util.percent v |> Util.cssDecl "margin"
    // and so on...

Two:

Maybe controversial, but the children being in a nested sublist isn't really that valuable from a coding ergonomics standpoint, beyond React doing it that way. It's an extra wasted indent that will compound on every nesting level.

It can be a mixed list using interfaces or DUs, the Html helpers can partition by attributes vs. elements and build the necessary React API objects without imposing that structure on the dev. (This also eliminates the need for the prop.content helper since you can just use Html.text directly.)

Alternative version of front page example:

let render state dispatch =
    Html.div [
        prop.id "main"
        prop.style [ style.padding 20 ]

        Html.button [
            prop.style [ style.marginRight 5 ]
            prop.onClick (fun _ -> dispatch Increment)
            Html.text "Increment"
        ]

        Html.button [
            prop.styleList [ style.marginLeft 5 ]
            prop.onClick (fun _ -> dispatch Decrement)
            Html.text "Decrement"
        ]

        Html.h1 state.Count
    ]

Three:

Any reason Html is capitalized while everything else is lower?

styleList and classList naming

The distinguishing feature of styleList and classList is not that they are lists, but that they are conditional.

I propose they be renamed styleIf and classIf or something similar.

Or, an alternative approach with helpers is maybe an option, to avoid having separate style and styleList/styleIf altogether. (Need a way to represent a false/null value in this scenario.) This is probably not much different from just using if-then-else but could be a bit easier to digest.

prop.style [
    style.display.flex |> ifCond model.isDisplayed
    style.display.none |> ifNotCond model.isDisplayed
]

Use Fable.React? (E.g. support for SSR)

I notice that Fable.React has special handling of server-side rendering, e.g.

https://github.com/fable-compiler/fable-react/blob/fa43005e82490945e2a59beedf86054ae1e929a3/src/Fable.React.Helpers.fs#L336-L342

Since Feliz does not directly use Fable.React for creating components but instead imports stuff itself, it might seem that any features found in Fable.React is not available in Feliz.

Should Feliz delegate component creation (and any other relevant functionality that might come up) to Fable.React in order to take advantage of the specifics of the Fable.React implementation?

Add ARIA props

ARIA props should be added, e.g. aria-labelledby, aria-describedby, aria-label, etc.

The properties are listed here (e.g. section 6.6).

I know basically nothing about these, just enough to know they are crucial to accessibility.

Add IRefValue overload to prop.ref and remove string overload

The ref prop should be able to accept a react ref hook, i.e., IRefValue<_>.

I suggest making it IRefValue<Element option> or IRefValue<HTMLElement option> (the latter is more specific, which is better if it's correct, but I'm not sure). The option wrapping is good because it forces devs to check whether the ref has been set.

Also, the string overload should be removed since it's legacy and likely to be removed.

I can make a PR if you let me know what you want. :)

Alternative fluent syntax

How do you feel about a syntax like this? (I drafted this quickly; there may be lots of room for improvement - for example, there may be a way to eliminate the .reactElement at the end of each component when more careful thought is given to this.)

AppBar()
  .position.absolute
  .children([
    Typography()
      .variant.h6
      .color.primary
      .text("Foo")
      .reactElement
  ])
  .reactElement

Benefits: Supports inheritance, and puts a final end to any discoverability issues still left in Feliz: No more hunting for the correct prop type; you're just dotting through and seeing what's available. Also supports overloaded props and enum props just like Feliz; we have basically just replaced property lists with "fluent builders".

Drawbacks: I don't know which impacts this has for bundle size or performance (see quick and dirty implementation below). Also it's "unusual" as far as Fable goes (no prop lists). Not that the latter matters by itself. Update: See more challenges here: #54 (comment)

I'm not saying we should necessarily do this; I just wanted to get this out of my head and post it for discussion.

For the record, below is the implementation of the above syntax. I have not made any attempts to inline or erase stuff. And there's a bit of type trickery which would likely cause OO fanatics to spin in their graves (what with subtypes inheriting base types parametrized by the subtype and all).

open Fable.Core.JsInterop
open Fable.React

let reactElement (el: ReactElementType) (props: 'a) : ReactElement =
  import "createElement" "react"


[<AbstractClass>]
type PropsBase() =
  abstract ElementType : ReactElementType
  member internal __.Props = ResizeArray<string * obj>()
  member internal this.reactElement =
    reactElement this.ElementType (createObj this.Props)


[<AbstractClass>]
type PropsBase<'a>() =
  inherit PropsBase()

  member this.custom (key: string) (value: obj) =
    this.Props.Add(key, value)
    this |> unbox<'a>

  member this.children (elems: seq<ReactElement>) =
    // should also call React.Children.ToArray
    this.custom "children" elems

  member this.text (text: string) =
    this.custom "children" text


type AppBarPosition<'a when 'a :> PropsBase<'a>>(parent: 'a) =
  member __.absolute = parent.custom "position" "absolute"
  member __.relative = parent.custom "position" "relative"


type AppBar() =
  inherit PropsBase<AppBar> ()
  member this.position = AppBarPosition(this)
  override __.ElementType = importDefault "@material-ui/core/AppBar"


type TypographyVariant<'a when 'a :> PropsBase<'a>>(parent: 'a) =
  member __.h5 = parent.custom "variant" "h5"
  member __.h6 = parent.custom "variant" "h6"

type TypographyColor<'a when 'a :> PropsBase<'a>>(parent: 'a) =
  member __.primary = parent.custom "color" "primary"
  member __.secondary = parent.custom "color" "secondary"


type Typography() =
  inherit PropsBase<Typography> ()
  member this.variant = TypographyVariant(this)
  member this.color = TypographyColor(this)
  override __.ElementType = importDefault "@material-ui/core/Typography"

Use units of measure instead of length.px etc.?

So this probably won't lead anywhere, but I wrote the whole issue before I did some calculations and saw how it might fail, and other people may come up with clever solutions, so here goes anyway:

One (slight) annoyance I have with Feliz is constructing CSS lengths:

style.height (length.px 200)

The following is more readable, using F#'s units-of-measure syntax:

style.height 200<px>

To accomplish this, we need to define all units:

// Could be auto-opened
module CssUnits =
  [<Measure>] type px
  [<Measure>] type em

Then, all members that now accept ICssUnit must instead have one overload for each unit:

  type style =
    static member inline height(value: int<px>) =
      Interop.mkStyle "height" ((unbox<string>value) + "px")
    static member inline height(value: float<px>) =  // ...
    static member inline height(value: int<em>) = // ...
    static member inline height(value: float<em>) = // ...

Since there are 32 different length members (including auto, which I don't have a good solution for), each style members that now has a single overload that accepts a single ICssUnit instead gets 32 overloads each, one per unit.

It should be fairly easy to write a generator for this, where you just have a list of units and partial member definitions, and generate all members/overloads from this.

Unfortunately, one big problem is the combinatorial effects of multi-parameter functions, such as style.margin(top, right, bottom, left). If we want to have all possible combinations of units, this method alone gets 1,048,576 overloads just from the unit combinations. That's of course completely out of the question. I don't know if there is a solution to that, other than requiring every parameter to be the same unit, making it linear, not polynomial.

One could of course have an intermediary helper function convert from any unit to the existing ICssUnit, e.g. style.height (length.parse 200<px>), but then we're back to scratch with the only difference that the unit is after the number (good) and it's more verbose and less discoverable (bad).

Feel free to just close this if you want.

PS: A question that pops to mind is what the length type and the ICssUnit actually accomplishes. If length.px 200 just converts to the string "200px", why not just write "200px" in the first place? What additional safety does the length type provide? (I mean, sure, it stops you from passing invalid strings like "foo" or "200invalid", but I can't imagine that will ever be a problem in practice.)

inheritFromParent -> inherit'

Since inherit is a reserved keyword, what about simply adding an apostrophe (inherit') instead of coming up with another name (inheritFromParent)?

Need tips on overload resolution

In Feliz.MaterialUI, I have a prop (Slider.step) which can be a number or null. Both ints and floats are relevant.

I currently have this:

type slider =
  static member inline step(value: int) = Interop.mkAttr "step" value
  static member inline step(value: float) = Interop.mkAttr "step" value
  static member inline step(value: int option) = Interop.mkAttr "step" value
  static member inline step(value: float option) = Interop.mkAttr "step" value

This works great in the following examples:

slider.step 0.5
slider.step (if model.X then Some 2 else None)

But overload resolution fails in the following example:

slider.step None

A workaround is to write

slider.step (None: int option)

which I don't like since it's user-facing.

I can also implement one of the option members as an extension member, but I'd rather not if it can be helped (would complicate the generator). Might turn out to be best, though.

Just wondering if you have a tip on how to best handle API design in these cases.

NuGet package fails to install

NuGet package fails to install for me for any version. I don't think it's environmental, I can install other packages. Using Windows 10, .NET Core SDK 2.2.301.

dotnet add package Feliz --version 0.18.0
Writing C:\Users\zaphod\AppData\Local\Temp\tmpA1D6.tmp
info : Adding PackageReference for package 'Feliz' into project 'C:\src\HtmlCssDsl\HtmlCssDsl\HtmlCssDsl.fsproj'.
info : Restoring packages for C:\src\HtmlCssDsl\HtmlCssDsl\HtmlCssDsl.fsproj...
info : Package 'Feliz' is compatible with all the specified frameworks in project 'C:\src\HtmlCssDsl\HtmlCssDsl\HtmlCssDsl.fsproj'.
error: Value cannot be null.
error: Parameter name: path1

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.