/
FilterDropdown.purs
170 lines (151 loc) · 5.83 KB
/
FilterDropdown.purs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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
module Lumi.Components.Table.FilterDropdown where
import Prelude hiding (div)
import Control.Alt ((<|>))
import Data.Array (drop, mapWithIndex, take, (!!))
import Data.Foldable (for_)
import Data.Maybe (Maybe(..), fromMaybe, maybe)
import Data.Nullable (Nullable, toMaybe)
import Effect.Uncurried (EffectFn1, EffectFn2, mkEffectFn2, runEffectFn1, runEffectFn2)
import Lumi.Components.Size (small)
import Lumi.Components.Icon (IconType(Rearrange), icon)
import Lumi.Components.Input (CheckboxState(..), checkbox, input)
import React.Basic (Component, JSX, createComponent, element, keyed, makeStateless)
import React.Basic.DOM (CSS, css, div, text, unsafeCreateDOMComponent)
import React.Basic.DOM.Events (targetChecked)
import React.Basic.Events as Events
import React.Basic.ReactDND (DragDrop, DragDropItemType(DragDropItemType), createDragDrop)
type FilterDropdownProps =
{ items :: Array Item
, onChange :: EffectFn1 (Array Item) Unit
, style :: CSS
}
component :: Component FilterDropdownProps
component = createComponent "TableFilterDropdown"
filterDropdown :: FilterDropdownProps -> JSX
filterDropdown = makeStateless component render
where
render props =
element (unsafeCreateDOMComponent "lumi-filter-dropdown")
{ style: props.style
, children:
props.items `flip mapWithIndex` \index item ->
keyed item.name $ filterItem_
{ index
, item
, items: props.items
, onChange: props.onChange
, onDrag: mkEffectFn2 onDrag
}
}
where
onDrag (DragIndex dragIndex) (HoverIndex hoverIndex) = do
runEffectFn1 props.onChange (moveItem dragIndex hoverIndex props.items)
moveItem :: forall a. Int -> Int -> Array a -> Array a
moveItem fromIndex toIndex items =
let
item = items !! fromIndex
items' = take fromIndex items <> drop (fromIndex + 1) items
in
take toIndex items'
<> maybe [] pure item
<> drop toIndex items'
filterDragDropType :: DragDropItemType
filterDragDropType = DragDropItemType "FILTER_ITEM"
dnd :: DragDrop { name :: String, index :: Int }
dnd = createDragDrop filterDragDropType
newtype DragIndex = DragIndex Int
newtype HoverIndex = HoverIndex Int
type Item =
{ name :: String
, label :: Nullable String
, filterLabel :: Nullable String
, hidden :: Boolean
}
type FilterItemProps =
{ item :: Item
, index :: Int
, items :: Array Item
, onChange :: EffectFn1 (Array Item) Unit
, onDrag :: EffectFn2 DragIndex HoverIndex Unit
}
filterItemComponent :: Component FilterItemProps
filterItemComponent = createComponent "FilterItem"
filterItem_ :: FilterItemProps -> JSX
filterItem_ = makeStateless filterItemComponent render
where
render { onChange, onDrag, items, item, index } =
dnd.dragSource
{ beginDrag: \_ -> pure
{ name: item.name
, index
}
, endDrag: const (pure unit)
, canDrag: const (pure true)
, isDragging: \{ item: draggingItem } ->
pure $ maybe false (\i -> i.name == item.name) draggingItem
, render: \{ connectDragSource, isDragging } ->
dnd.dropTarget
{ drop: handleDrop onDrag index
, hover: const (pure unit)
, canDrop: const (pure true)
, render: \{ connectDropTarget, isOver, item: maybeDragItem } ->
connectDragSource $ connectDropTarget $
element (unsafeCreateDOMComponent "lumi-row")
{ className: if item.hidden then "" else "active"
, style: css
{ padding: "0 0.8rem 0 0"
, alignItems: "center"
, borderTop:
if isOver && (fromMaybe false ((\dragItem -> dragItem.index > index) <$> maybeDragItem))
then "0.2rem solid #0044e4"
else "0.2rem solid transparent"
, borderBottom:
if isOver && (fromMaybe false ((\dragItem -> dragItem.index < index) <$> maybeDragItem))
then "0.2rem solid #0044e4"
else "0.2rem solid transparent"
, opacity: if isDragging then 0.1 else 1.0
}
, children:
[ renderInput onChange items item
, renderLabel item
, renderDragIcon
]
}
}
}
handleDrop onDrag index { item: dragItem } = do
for_ (_.index <$> dragItem) \dragIndex ->
runEffectFn2 onDrag (DragIndex dragIndex) (HoverIndex index)
pure Nothing
renderInput onChange items item =
input checkbox
{ size = small
, disabled = not maybe false (const true) (toMaybe item.label)
, checked = if item.hidden then Off else On
, onChange =
Events.handler targetChecked
(fromMaybe false >>> handleCheckboxChange onChange items item)
}
handleCheckboxChange onChange items item checked = do
runEffectFn1 onChange $ items <#> \item_ ->
if item.name == item_.name
then item_ { hidden = not checked }
else item_
renderLabel item =
div
{ style: css
{ paddingLeft: "0.8rem"
, paddingRight: "0.8rem"
, fontSize: "1.2rem"
, flex: 1
, whiteSpace: "nowrap"
, overflow: "hidden"
, textOverflow: "ellipsis"
}
, children: [ text (fromMaybe "" (toMaybe item.filterLabel <|> toMaybe item.label)) ]
}
renderDragIcon =
icon
{ type_: Rearrange
, style: css { cursor: "move" }
}