FormSelect.purs 5.18 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)
9
import Data.Map as Map
arturo's avatar
arturo committed
10
import Data.Maybe (Maybe(..))
11
import Data.Tuple (Tuple(..))
12 13 14
import Effect (Effect)
import Gargantext.Components.Bootstrap.Types (ComponentStatus(..), Sizing(..))
import Gargantext.Utils.Reactix as R2
15
import Gargantext.Utils.Show as GUS
16
import Reactix as R
arturo's avatar
arturo committed
17
import Reactix.DOM.HTML as H
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 57 58 59
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
60
-- |     here. Please use `formSelect'` for any other type
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
-- |
-- | 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
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
    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

94 95 96 97 98
    -- Render
    pure $
      R2.select
      { className
      , on: { change }
arturo's avatar
arturo committed
99
      , disabled: elem status [ Disabled, Idled ]
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
      , 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
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
  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.
146
     Show a
arturo's avatar
arturo committed
147 148 149 150
  => R2.OptComponent Options (AnyTypeProps a) r
formSelect' = R2.optComponent component' options

component' :: forall a.
151
     Show a
arturo's avatar
arturo committed
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
  => 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
174
      change = onChange' status reader callback
arturo's avatar
arturo committed
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193

    -- 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 ]
      )
194 195
    where
      reader = GUS.reader list
arturo's avatar
arturo committed
196 197 198 199 200 201

-- | * 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.
202
     Show a
arturo's avatar
arturo committed
203
  => ComponentStatus
204
  -> (String -> Maybe a)
arturo's avatar
arturo committed
205 206 207
  -> (a -> Effect Unit)
  -> event
  -> Effect Unit
208
onChange' status reader callback event = do
arturo's avatar
arturo committed
209
  if   status == Enabled
210
  then event # unsafeCoerce >>> _.target.value >>> reader >>> case _ of
arturo's avatar
arturo committed
211 212 213
    Nothing -> R.nothing
    Just v  -> callback v
  else R.nothing