{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE ExtendedDefaultRules #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-type-defaults #-} module Shpadoinkle.Widgets.Form.Dropdown where import Control.Compactable import Data.Aeson import Data.Text import GHC.Generics import Prelude hiding (div) #ifdef TESTING import Test.QuickCheck (Arbitrary (..)) #endif import Shpadoinkle import Shpadoinkle.Html hiding (p, s, s', select, select', selected) import Shpadoinkle.Keyboard import Shpadoinkle.Widgets.Types default (Text) data Dropdown p a = Dropdown { _considered :: ConsideredChoice p a , _toggle :: Toggle } deriving instance (Show (Selected p a), Show (Considered p a), Show a) => Show (Dropdown p a) deriving instance (Read (Selected p a), Read (Considered p a), Read a, Ord a) => Read (Dropdown p a) deriving instance (Eq (Selected p a), Eq (Considered p a), Eq a) => Eq (Dropdown p a) deriving instance (Ord (Selected p a), Ord (Considered p a), Ord a) => Ord (Dropdown p a) deriving instance (Foldable (ConsideredChoice p)) => Foldable (Dropdown p) deriving instance Generic (Dropdown p a) instance (ToJSON a, ToJSON (Selected p a), ToJSON (Considered p a)) => ToJSON (Dropdown p a) instance (FromJSON a, FromJSON (Selected p a), FromJSON (Considered p a), Ord a) => FromJSON (Dropdown p a) instance (NFData (Selected p a), NFData (ConsideredChoice p a), NFData a) => NFData (Dropdown p a) instance Control (Dropdown 'One) where type Val (Dropdown 'One) a = Maybe a hygiene :: Applicative f => (Hygiene -> f Hygiene) -> Dropdown 'One a -> f (Dropdown 'One a) hygiene f d = (\x -> d {_toggle = Closed x }) <$> f (togHygiene $ _toggle d) value :: (Applicative f, Ord a) => (Maybe a -> f (Maybe a)) -> Dropdown 'One a -> f (Dropdown 'One a) value f d = maybe d (select' d) <$> f (selected d) instance Control (Dropdown 'AtleastOne) where hygiene f d = (\x -> d {_toggle = Closed x }) <$> f (togHygiene $ _toggle d) value f d = select' d <$> f (selected d) instance (Consideration ConsideredChoice p, Ord a) => IsToggle (Dropdown p a) where close p = shrug $ p { _toggle = close (_toggle p) } toggle p = shrug $ p { _toggle = toggle (_toggle p) } open p = shrug $ p { _toggle = open (_toggle p) } data Config m = Config { _attrs :: forall a. [(Text, Prop m a)] , _clickAway :: ClickAway } defConfig :: Config m defConfig = Config [] ClosesOnClickAway instance (Compactable (ConsideredChoice p)) => Compactable (Dropdown p) where compact (Dropdown c t) = Dropdown (compact c) t separate (Dropdown c t) = let (l,r) = separate c in (Dropdown l t, Dropdown r t) filter p (Dropdown c t) = Dropdown (Control.Compactable.filter p c) t partition p (Dropdown c t) = let (l, r) = Control.Compactable.partition p c in (Dropdown l t, Dropdown r t) instance Semigroup (ConsideredChoice p a) => Semigroup (Dropdown p a) where Dropdown c t <> Dropdown c' t' = Dropdown (c <> c') (t <> t') instance Monoid (ConsideredChoice p a) => Monoid (Dropdown p a) where mempty = Dropdown mempty mempty instance SetLike (ConsideredChoice p) => SetLike (Dropdown p) where toSet = toSet . _considered smap f (Dropdown c t) = Dropdown (smap f c) t valid (Dropdown c _) = valid c instance (Consideration ConsideredChoice p, PickToSelected p) => Selection Dropdown p where select (Dropdown c t) x = close $ Dropdown (select c x) t unselected = unselected . _considered selected = selected . _considered withOptions x xs = Dropdown (x `withOptions` xs) mempty retain (Dropdown c t) (Dropdown c' t') = Dropdown (retain c c') (t <> t') instance (Consideration ConsideredChoice p, Deselection ConsideredChoice p) => Deselection Dropdown p where noselection xs = Dropdown (noselection xs) mempty deselect (Dropdown c t) = close $ Dropdown (deselect c) t instance (Consideration ConsideredChoice p, PickToConsidered p) => Consideration Dropdown p where consider x (Dropdown c t) = Dropdown (consider x c) t choose (Dropdown c t) = Dropdown (choose c) t choice (Dropdown c _) = choice c considered (Dropdown c _) = considered c shrug (Dropdown c xs) = Dropdown (shrug c) xs data Theme m p b = Theme { _wrapper :: forall a . [Html m a] -> Html m a , _header :: forall a . Selected p b -> [Html m a] , _list :: forall a . [Html m a] -> Html m a , _item :: forall a . b -> Html m a } semantic :: Present b => Present (Selected p b) => Dropdown p b -> Theme m p b semantic Dropdown {..} = Theme { _wrapper = div [ class' [ ("dropdown", True) , ("ui", True) , ("active", _toggle == Open) ] ] , _header = \cs -> [ div [ class' "text" ] (present cs) , i' [ class' ["dropdown", "icon"] ] ] , _list = div [ class' [ "menu" , "transition" ] ] , _item = div [ class' "item" ] . present } act :: ( Considered p ~ Maybe , Consideration ConsideredChoice p , Consideration Dropdown p , Ord a) => Dropdown p a -> Dropdown p a act x | _toggle x == Open = close $ case considered x of Just _ -> choose x _ -> x act x = open x dropdown :: ( Considered p ~ Maybe , Consideration Dropdown p , Consideration ConsideredChoice p , Ord a ) => (Dropdown p a -> Theme m p a) -> Config m -> Dropdown p a -> Html m (Dropdown p a) dropdown toTheme Config {..} x = let Theme {..} = toTheme x ifClickAway = case _clickAway of ClosesOnClickAway -> [ onClickAway close ] StaysOpenOnClickAway -> [] in injectProps ([onKeyup $ \case Enter -> act UpArrow -> considerPrev DownArrow -> considerNext _ -> id , onClick act , tabbable ] ++ ifClickAway ++ _attrs) . _wrapper $ _header (selected x) ++ [ _list $ (\y -> injectProps [ onMouseover (consider' y) , onFocus (consider' y) , tabbable ] . _item $ y) <$> toList (unselected x) ] #ifdef TESTING instance (Ord a, Arbitrary a, Arbitrary (ConsideredChoice p a)) => Arbitrary (Dropdown p a) where arbitrary = Dropdown <$> arbitrary <*> arbitrary #endif