Skip to content
Prev Previous commit
Next Next commit
Simplify code for option parsing
  • Loading branch information
alfonsogarciacaro committed Jan 23, 2019
commit d01130996ad41a28c0854fc022172db38cb16b15
61 changes: 38 additions & 23 deletions src/Fulma/Common.fs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace Fulma

open Fable.Import.React
open Fable.Helpers.React.Props

[<RequireQualifiedAccess>]
Expand Down Expand Up @@ -339,7 +340,7 @@ module Modifier =
| IsShadowless
| IsUnselectable -> (Fable.Core.Reflection.getCaseName opt)::result

options |> List.fold parseOption [] |> List.map Some
options |> List.fold parseOption []

[<AutoOpen>]
module Common =
Expand All @@ -349,23 +350,43 @@ module Common =
| Modifiers of Modifier.IModifier list

type GenericOptions =
{ CustomClass : string option
Props : IHTMLProp list
Modifiers : string option list }
{ Props : IHTMLProp list
Classes : string list }

static member Empty =
{ CustomClass = None
Props = []
Modifiers = [] }
{ Props = []; Classes = [] }

let genericParse options =
let parseOptions (result: GenericOptions ) opt =
match opt with
| Props props -> { result with Props = props }
| CustomClass customClass -> { result with CustomClass = Some customClass }
| Modifiers modifiers -> { result with Modifiers = modifiers |> Modifier.parseModifiers }
static member Parse(options, parser, ?baseClass) =
let result = options |> List.fold parser GenericOptions.Empty
match baseClass with
| Some baseClass -> result.AddClass(baseClass)
| None -> result

member this.AddProp(prop) =
{ this with Props = prop::this.Props }

member this.AddProps(props) =
{ this with Props = [email protected] }

member this.AddClass(cl: string) =
{ this with Classes = cl::this.Classes }

member this.AddCaseName(case: obj) =
Fable.Core.Reflection.getCaseName case |> this.AddClass

member this.AddModifiers(modifiers) =
{ this with Classes = (modifiers |> Modifier.parseModifiers) @ this.Classes }

member this.ToReactElement(el, ?children): ReactElement =
let children = defaultArg children []
// TODO: Remove empty classes?
let classes = String.concat " " this.Classes |> ClassName :> IHTMLProp
el (classes::this.Props) children

options |> List.fold parseOptions GenericOptions.Empty
let parseOption (result: GenericOptions ) = function
| Props props -> result.AddProps props
| CustomClass customClass -> result.AddClass customClass
| Modifiers modifiers -> result.AddModifiers modifiers

module Helpers =

Expand All @@ -381,16 +402,10 @@ module Text =
open Fable.Helpers.React

let p (options: GenericOption list) children =
let opts = genericParse options
let classes = Helpers.classes "" ( opts.CustomClass::opts.Modifiers ) []
p (classes::opts.Props) children
GenericOptions.Parse(options, parseOption).ToReactElement(p, children)

let div (options: GenericOption list) children =
let opts = genericParse options
let classes = Helpers.classes "" ( opts.CustomClass::opts.Modifiers ) []
div (classes::opts.Props) children
GenericOptions.Parse(options, parseOption).ToReactElement(div, children)

let span (options: GenericOption list) children =
let opts = genericParse options
let classes = Helpers.classes "" ( opts.CustomClass::opts.Modifiers ) []
span (classes::opts.Props) children
GenericOptions.Parse(options, parseOption).ToReactElement(span, children)
110 changes: 28 additions & 82 deletions src/Fulma/Components/Breadcrumb.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,113 +9,59 @@ module Breadcrumb =

module Classes =
let [<Literal>] Container = "breadcrumb"
module Alignment =
let [<Literal>] IsCentered = "is-centered"
let [<Literal>] IsRight = "is-right"
module Separator =
/// Alias for: has-arrow-separator
let [<Literal>] Arrow = "has-arrow-separator"
/// Alias for: has-bullet-separator
let [<Literal>] Bullet = "has-bullet-separator"
/// Alias for: has-dot-separator
let [<Literal>] Dot = "has-dot-separator"
/// Alias for: has-succeeds-separator
let [<Literal>] Succeeds = "has-succeeds-separator"
module State =
let [<Literal>] IsActive = "is-active"

type Option =
/// Add `is-centered` class
| IsCentered
| [<CompiledName("is-centered")>] IsCentered
/// Add `is-right` class
| IsRight
| [<CompiledName("is-right")>] IsRight
/// Add `has-arrow-separator` class
| HasArrowSeparator
| [<CompiledName("has-arrow-separator")>] HasArrowSeparator
/// Add `has-bullet-separator` class
| HasBulletSeparator
| [<CompiledName("has-bullet-separator")>] HasBulletSeparator
/// Add `has-dot-separator` class
| HasDotSeparator
| [<CompiledName("has-dot-separator")>] HasDotSeparator
/// Add `has-succeeds-separator` class
| HasSucceedsSeparator
| [<CompiledName("has-succeeds-separator")>] HasSucceedsSeparator
| Size of ISize
| Props of IHTMLProp list
| CustomClass of string
| Modifiers of Modifier.IModifier list

type internal Options =
{ Props : IHTMLProp list
Alignment : string option
Separator : string option
Size : string option
CustomClass : string option
Modifiers : string option list }

static member Empty =
{ Props = []
Alignment = None
Separator = None
Size = None
CustomClass = None
Modifiers = [] }

/// Generate <nav class="breadcumb"></nav>
let breadcrumb options children =
let parseOptions result =
function
| IsCentered -> { result with Alignment = Classes.Alignment.IsCentered |> Some }
| IsRight -> { result with Alignment = Classes.Alignment.IsRight |> Some }
let parseOption (result: GenericOptions) = function
| Props props -> result.AddProps props
| Size size -> ofSize size |> result.AddClass
| CustomClass customClass -> result.AddClass customClass
| Modifiers modifiers -> result.AddModifiers modifiers
| IsCentered
| IsRight
// Separators
| HasArrowSeparator -> { result with Separator = Classes.Separator.Arrow |> Some }
| HasBulletSeparator -> { result with Separator = Classes.Separator.Bullet |> Some }
| HasDotSeparator -> { result with Separator = Classes.Separator.Dot |> Some }
| HasSucceedsSeparator -> { result with Separator = Classes.Separator.Succeeds |> Some }
| Size size -> { result with Size = ofSize size |> Some }
| Props props -> { result with Props = props }
| CustomClass customClass -> { result with CustomClass = Some customClass }
| Modifiers modifiers -> { result with Modifiers = modifiers |> Modifier.parseModifiers }
| HasArrowSeparator
| HasBulletSeparator
| HasDotSeparator
| HasSucceedsSeparator as opt -> result.AddCaseName opt

let opts = options |> List.fold parseOptions Options.Empty
let classes = Helpers.classes
Classes.Container
( opts.Alignment::opts.Separator::opts.Size::opts.CustomClass::opts.Modifiers )
[ ]

nav (classes::opts.Props)
[ ul [ ] children ]
GenericOptions.Parse(options, parseOption, Classes.Container)
.ToReactElement(nav, [ul [] children])

module Item =

type Option =
/// Add `is-active` class if true
| IsActive of bool
| [<CompiledName("is-active")>] IsActive of bool
| Props of IHTMLProp list
| CustomClass of string
| Modifiers of Modifier.IModifier list

type internal Options =
{ Props : IHTMLProp list
IsActive : bool
CustomClass : string option
Modifiers : string option list }

static member Empty =
{ Props = []
IsActive = false
CustomClass = None
Modifiers = [] }

/// Generate <li></li>
let item (options: Item.Option list) children =
let parseOptions (result: Item.Options) =
function
| Item.IsActive state -> { result with IsActive = state }
| Item.Props props -> { result with Props = props }
| Item.CustomClass customClass -> { result with CustomClass = Some customClass }
| Item.Modifiers modifiers -> { result with Modifiers = modifiers |> Modifier.parseModifiers }

let opts = options |> List.fold parseOptions Item.Options.Empty

li [ yield Helpers.classes "" ( opts.CustomClass::opts.Modifiers )
[ Classes.State.IsActive, opts.IsActive ]
yield! opts.Props ]
children
let parseOption (result: GenericOptions) opt =
match opt with
| Item.IsActive state -> if state then result.AddCaseName opt else result
| Item.Props props -> result.AddProps props
| Item.CustomClass customClass -> result.AddClass customClass
| Item.Modifiers modifiers -> result.AddModifiers modifiers

GenericOptions.Parse(options, parseOption).ToReactElement(li, children)
106 changes: 18 additions & 88 deletions src/Fulma/Elements/Button.fs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ module Button =
let [<Literal>] Container = "button"
module List =
let [<Literal>] Container = "buttons"
module Size =
let [<Literal>] AreSmall = "are-small"
let [<Literal>] AreMedium = "are-medium"
let [<Literal>] AreLarge = "are-large"

type Option =
// Colors
Expand Down Expand Up @@ -52,68 +48,32 @@ module Button =
| CustomClass of string
| Modifiers of Modifier.IModifier list

type internal Options =
{ Level : string option
Size : string option
IsDisabled : bool
Props : IHTMLProp list
CustomClass : string option
OnClick : (MouseEvent -> unit) option
Modifiers : string option list }
static member Empty =
{ Level = None
Size = None
IsDisabled = false
Props = []
CustomClass = None
OnClick = None
Modifiers = [] }

let private addClass (option: Option) (result: Options) =
{ result with Modifiers = (Fable.Core.Reflection.getCaseName option |> Some)::result.Modifiers }

let internal btnView element (options : Option list) children =
let parseOption (result : Options) opt =
let parseOption (result : GenericOptions) opt =
match opt with
| Color color -> { result with Level = ofColor color |> Some }
// Sizes
| Size size -> { result with Size = ofSize size |> Some }
| Color color -> ofColor color |> result.AddClass
| Size size -> ofSize size |> result.AddClass
// Styles
| IsLink -> { result with Level = Fable.Core.Reflection.getCaseName opt |> Some }
| IsLink
| IsFullWidth
| IsOutlined
| IsInverted
| IsText
| IsRounded
| IsExpanded -> addClass opt result
| IsExpanded -> result.AddCaseName opt
// States
| IsHovered state
| IsFocused state
| IsActive state
| IsLoading state
| IsStatic state -> if state then addClass opt result else result
| Disabled isDisabled -> { result with IsDisabled = isDisabled }
| Props props -> { result with Props = props }
| CustomClass customClass -> { result with CustomClass = Some customClass }
| OnClick cb -> { result with OnClick = cb |> Some }
| Modifiers modifiers -> { result with Modifiers = modifiers |> Modifier.parseModifiers }
| IsStatic state -> if state then result.AddCaseName opt else result
| Disabled isDisabled -> Fable.Helpers.React.Props.Disabled isDisabled |> result.AddProp
| Props props -> result.AddProps props
| CustomClass customClass -> result.AddClass customClass
| OnClick cb -> DOMAttr.OnClick cb |> result.AddProp
| Modifiers modifiers -> { result with Classes = (modifiers |> Modifier.parseModifiers)@result.Classes }

let opts = options |> List.fold parseOption Options.Empty
let classes = Helpers.classes
Classes.Container
( opts.Level
::opts.Size
::opts.CustomClass
::opts.Modifiers )
[ ]

element
[ yield classes
yield Fable.Helpers.React.Props.Disabled opts.IsDisabled :> IHTMLProp
if Option.isSome opts.OnClick then
yield DOMAttr.OnClick opts.OnClick.Value :> IHTMLProp
yield! opts.Props ]
children
GenericOptions.Parse(options, parseOption, Classes.Container).ToReactElement(element, children)

/// Generate <button class="button"></button>
let button options children = btnView button options children
Expand Down Expand Up @@ -161,45 +121,15 @@ module Button =
| CustomClass of string
| Modifiers of Modifier.IModifier list

type internal Options =
// Size : string option
{ Props : IHTMLProp list
CustomClass : string option
Modifiers : string option list }

static member Empty =
// Size = None
{ Props = [ ]
CustomClass = None
Modifiers = [] }

let internal ofSize size =
match size with
| IsSmall -> Classes.List.Size.AreSmall
| IsMedium -> Classes.List.Size.AreMedium
| IsLarge -> Classes.List.Size.AreLarge

let private addListClass (option: List.Option) (result: List.Options) =
{ result with Modifiers = (Fable.Core.Reflection.getCaseName option |> Some)::result.Modifiers }

/// Generate <div class="buttons"></div>
let list (options : List.Option list) children =
let parseOption (result : List.Options) opt =
let parseOption (result : GenericOptions) opt =
match opt with
| List.HasAddons
| List.IsCentered
| List.IsRight -> addListClass opt result
| List.Props props -> { result with Props = props }
| List.CustomClass customClass -> { result with CustomClass = Some customClass }
| List.Modifiers modifiers -> { result with Modifiers = modifiers |> Modifier.parseModifiers }
// | List.Size size -> { result with Size = List.ofSize size |> Some }

let opts = options |> List.fold parseOption List.Options.Empty
let classes = Helpers.classes
Classes.List.Container
( opts.CustomClass
// ::opts.Size
::opts.Modifiers )
[ ]
| List.IsRight -> Fable.Core.Reflection.getCaseName opt |> result.AddClass
| List.Props props -> result.AddProps props
| List.CustomClass customClass -> result.AddClass customClass
| List.Modifiers modifiers -> result.AddModifiers modifiers

div (classes::opts.Props) children
GenericOptions.Parse(options, parseOption, Classes.List.Container).ToReactElement(div, children)
Loading