FormSelect.purs 5.05 KB
Newer Older
arturo's avatar
arturo committed
1 2 3 4
module Gargantext.Components.Bootstrap.FormSelect
  ( formSelect
  , formSelect'
  ) where
5 6 7 8

import Gargantext.Prelude

import Data.Foldable (elem, intercalate)
arturo's avatar
arturo committed
9
import Data.Maybe (Maybe(..))
10 11 12 13
import Effect (Effect)
import Gargantext.Components.Bootstrap.Types (ComponentStatus(..), Sizing(..))
import Gargantext.Utils.Reactix as R2
import Reactix as R
arturo's avatar
arturo committed
14
import Reactix.DOM.HTML as H
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
import Unsafe.Coerce (unsafeCoerce)

type Props =
  ( callback    :: String -> Effect Unit
  , value       :: String
  | Options
  )

type Options =
  ( status      :: ComponentStatus
  , className   :: String
  , type        :: String
  , placeholder :: String
  , size        :: Sizing
  )

options :: Record Options
options =
  { status      : Enabled
  , className   : ""
  , type        : "text"
  , placeholder : ""
  , size        : MediumSize
  }

-- | Structural Component for the Bootstrap select
-- |
-- |  ```purescript
-- |  formSelect { callback, value }
-- |  [
-- |    H.option
-- |    { value: "foo" }
-- |    [ H.text "foo" ]
-- |  ,
-- |    H.option
-- |    { value: "bar" }
-- |    [ H.text "bar" ]
-- |  ]
-- |  ```
-- |
-- |
-- | (?) note that it handled `value` as a String, as it is the KISS solution
arturo's avatar
arturo committed
57
-- |     here. Please use `formSelect'` for any other type
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
-- |
-- | https://getbootstrap.com/docs/4.1/components/forms/
formSelect :: forall r. R2.OptComponent Options Props r
formSelect = R2.optComponent component options

componentName :: String
componentName = "b-form-select"

bootstrapName :: String
bootstrapName = "form-control"

component :: R.Component Props
component = R.hooksComponent componentName cpt where
  cpt props@{ callback
            , status
            } children = do
    -- Computed
arturo's avatar
arturo committed
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
    let
      className = intercalate " "
        -- provided custom className
        [ props.className
        -- BEM classNames
        , componentName
        , componentName <> "--" <> show status
        -- Bootstrap specific classNames
        , bootstrapName
        , bootstrapName <> "-" <> show props.size
        ]

    -- Behaviors
    let
      change = onChange status callback

91 92 93 94 95
    -- Render
    pure $
      R2.select
      { className
      , on: { change }
arturo's avatar
arturo committed
96
      , disabled: elem status [ Disabled, Idled ]
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
      , readOnly: elem status [ Idled ]
      , type: props.type
      , value: props.value
      }
      children

-- | * Change event will effectively be triggered according to the
-- | component status props
-- | * Also directly returns the newly input value
-- | (usage not so different from `targetValue` of ReactBasic)
onChange :: forall event.
     ComponentStatus
  -> (String -> Effect Unit)
  -> event
  -> Effect Unit
onChange status callback event = do
  if   status == Enabled
  then callback $ (unsafeCoerce event).target.value
arturo's avatar
arturo committed
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
  else R.nothing


-----------------------------------------------------------------------

type AnyTypeProps a =
  ( callback :: a -> Effect Unit
  , value :: a
  , list :: Array a
  | Options
  )

-- | Derived component for `formSelect` with any value type (with `Read`
-- | and `Show` constraint)
-- |
-- |
-- |  ```purescript
-- |  formSelect'
-- |  { callback: flip T.write_ box
-- |  , value: anyType
-- |  , list: [ anyType, anyType, ... ]
-- |  }
-- |  ```
-- |
-- |  (?) Note that HTML option tags will be automatically added thanks to
-- |      to the provided `list` prop. You can add additional HTML option within
-- |      the `children` prop
formSelect' :: forall r a.
     Read a
  => Show a
  => R2.OptComponent Options (AnyTypeProps a) r
formSelect' = R2.optComponent component' options

component' :: forall a.
     Read a
  => Show a
  => R.Component (AnyTypeProps a)
component' = R.hooksComponent (componentName <> "__helper") cpt where
  cpt props@{ callback
            , list
            , status
            , value
            } children = do
    -- Computed
    let
      className = intercalate " "
      -- provided custom className
        [ props.className
        -- BEM classNames
        , componentName
        , componentName <> "--" <> show status
        -- Bootstrap specific classNames
        , bootstrapName
        , bootstrapName <> "-" <> show props.size
        ]

    -- Behaviors
    let
      change = onChange' status callback

    -- Render
    pure $
      R2.select
      { className
      , on: { change }
      , disabled: elem status [ Disabled ]
      , readOnly: elem status [ Idled ]
      , type: props.type
      , value: show value
      }
      (
        children
      <>
        flip map list \raw ->
          H.option
          { value: show raw }
          [ H.text $ show raw ]
      )

-- | * Change event will effectively be triggered according to the
-- | component status props
-- | * Also directly returns the newly input value
-- | (usage not so different from `targetValue` of ReactBasic)
onChange' :: forall event a.
     Read a
  => Show a
  => ComponentStatus
  -> (a -> Effect Unit)
  -> event
  -> Effect Unit
onChange' status callback event = do
  if   status == Enabled
  then event # unsafeCoerce >>> _.target.value >>> read >>> case _ of
    Nothing -> R.nothing
    Just v  -> callback v
  else R.nothing