fron-ui 1.0.0rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +38 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/.yardopts +8 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +105 -0
- data/Rakefile +37 -0
- data/Readme.md +4 -0
- data/db.json +192 -0
- data/fron-ui.gemspec +21 -0
- data/lib/fron-ui.rb +1 -0
- data/lib/fron_ui.rb +5 -0
- data/lib/fron_ui/version.rb +7 -0
- data/opal/fron-ui/base.rb +49 -0
- data/opal/fron-ui/behaviors/action.rb +40 -0
- data/opal/fron-ui/behaviors/actions.rb +40 -0
- data/opal/fron-ui/behaviors/confirmation.rb +23 -0
- data/opal/fron-ui/behaviors/dropdown.rb +27 -0
- data/opal/fron-ui/behaviors/file.rb +48 -0
- data/opal/fron-ui/behaviors/intendable_children.rb +76 -0
- data/opal/fron-ui/behaviors/keydown.rb +31 -0
- data/opal/fron-ui/behaviors/loop.rb +41 -0
- data/opal/fron-ui/behaviors/render.rb +30 -0
- data/opal/fron-ui/behaviors/rest.rb +121 -0
- data/opal/fron-ui/behaviors/selectable_children.rb +67 -0
- data/opal/fron-ui/behaviors/serialize.rb +32 -0
- data/opal/fron-ui/behaviors/shortcuts.rb +35 -0
- data/opal/fron-ui/behaviors/state.rb +56 -0
- data/opal/fron-ui/behaviors/transition.rb +63 -0
- data/opal/fron-ui/components/action.rb +18 -0
- data/opal/fron-ui/components/box.rb +17 -0
- data/opal/fron-ui/components/button.rb +61 -0
- data/opal/fron-ui/components/calendar.rb +129 -0
- data/opal/fron-ui/components/checkbox.rb +57 -0
- data/opal/fron-ui/components/chooser.rb +246 -0
- data/opal/fron-ui/components/color_panel.rb +235 -0
- data/opal/fron-ui/components/color_picker.rb +111 -0
- data/opal/fron-ui/components/container.rb +61 -0
- data/opal/fron-ui/components/date_picker.rb +141 -0
- data/opal/fron-ui/components/drag.rb +76 -0
- data/opal/fron-ui/components/dropdown.rb +72 -0
- data/opal/fron-ui/components/icon.rb +29 -0
- data/opal/fron-ui/components/image.rb +77 -0
- data/opal/fron-ui/components/input.rb +30 -0
- data/opal/fron-ui/components/label.rb +9 -0
- data/opal/fron-ui/components/list.rb +34 -0
- data/opal/fron-ui/components/loader.rb +63 -0
- data/opal/fron-ui/components/modal.rb +0 -0
- data/opal/fron-ui/components/notifications.rb +73 -0
- data/opal/fron-ui/components/number.rb +202 -0
- data/opal/fron-ui/components/progress.rb +52 -0
- data/opal/fron-ui/components/slider.rb +47 -0
- data/opal/fron-ui/components/tabs.rb +149 -0
- data/opal/fron-ui/components/textarea.rb +13 -0
- data/opal/fron-ui/components/time.rb +65 -0
- data/opal/fron-ui/components/title.rb +34 -0
- data/opal/fron-ui/examples/blog/index.rb +289 -0
- data/opal/fron-ui/examples/comments/components/comment.rb +75 -0
- data/opal/fron-ui/examples/comments/components/comments.rb +93 -0
- data/opal/fron-ui/examples/comments/components/footer.rb +36 -0
- data/opal/fron-ui/examples/comments/components/header.rb +35 -0
- data/opal/fron-ui/examples/comments/components/list.rb +12 -0
- data/opal/fron-ui/examples/comments/index.rb +6 -0
- data/opal/fron-ui/examples/contacts/components/contacts.rb +100 -0
- data/opal/fron-ui/examples/contacts/components/details.rb +92 -0
- data/opal/fron-ui/examples/contacts/components/item.rb +46 -0
- data/opal/fron-ui/examples/contacts/components/list.rb +10 -0
- data/opal/fron-ui/examples/contacts/components/sidebar.rb +30 -0
- data/opal/fron-ui/examples/contacts/index.rb +6 -0
- data/opal/fron-ui/examples/editor/index.rb +164 -0
- data/opal/fron-ui/examples/kitchensink/index.rb +193 -0
- data/opal/fron-ui/examples/todos/components/item.rb +84 -0
- data/opal/fron-ui/examples/todos/components/options.rb +26 -0
- data/opal/fron-ui/examples/todos/components/todos.rb +145 -0
- data/opal/fron-ui/examples/todos/index.rb +6 -0
- data/opal/fron-ui/examples/webshop/index.rb +0 -0
- data/opal/fron-ui/fonts/ionicons.rb +2954 -0
- data/opal/fron-ui/fonts/open_sans.rb +19 -0
- data/opal/fron-ui/lib/collection.rb +138 -0
- data/opal/fron-ui/lib/date.rb +23 -0
- data/opal/fron-ui/lib/debounce.rb +14 -0
- data/opal/fron-ui/lib/image_loader.rb +13 -0
- data/opal/fron-ui/lib/lorem.rb +93 -0
- data/opal/fron-ui/lib/nil.rb +29 -0
- data/opal/fron-ui/lib/record.rb +23 -0
- data/opal/fron-ui/lib/state_serializer.rb +129 -0
- data/opal/fron-ui/lib/storage.rb +57 -0
- data/opal/fron-ui/spec/setup.rb +40 -0
- data/opal/fron-ui/ui.rb +40 -0
- data/opal/fron-ui/utils/theme_roller.rb +63 -0
- data/opal/fron-ui/vendor/autoprefixer.js +21114 -0
- data/opal/fron-ui/vendor/marked.js +1291 -0
- data/opal/fron-ui/vendor/md5.js +274 -0
- data/opal/fron-ui/vendor/moment.js +3083 -0
- data/opal/fron-ui/vendor/uuid.js +92 -0
- data/opal/fron_ui.rb +13 -0
- data/spec/behaviors/action_spec.rb +34 -0
- data/spec/behaviors/actions_spec.rb +38 -0
- data/spec/behaviors/confirmation_spec.rb +23 -0
- data/spec/behaviors/dropdown_spec.rb +32 -0
- data/spec/behaviors/render_spec.rb +20 -0
- data/spec/behaviors/rest_spec.rb +70 -0
- data/spec/behaviors/selectable_children_spec.rb +40 -0
- data/spec/behaviors/serialize_spec.rb +34 -0
- data/spec/components/action_spec.rb +7 -0
- data/spec/components/base_spec.rb +19 -0
- data/spec/components/box_spec.rb +7 -0
- data/spec/components/button_spec.rb +9 -0
- data/spec/components/calendar_spec.rb +58 -0
- data/spec/components/checkbox_spec.rb +20 -0
- data/spec/components/chooser_spec.rb +75 -0
- data/spec/components/color_panel_spec.rb +49 -0
- data/spec/components/color_picker_spec.rb +41 -0
- data/spec/components/container_spec.rb +23 -0
- data/spec/components/date_picker_spec.rb +71 -0
- data/spec/components/drag_spec.rb +20 -0
- data/spec/components/dropdown_spec.rb +33 -0
- data/spec/components/image_spec.rb +33 -0
- data/spec/components/input_spec.rb +8 -0
- data/spec/components/list_spec.rb +10 -0
- data/spec/components/loader_spec.rb +9 -0
- data/spec/components/notifications_spec.rb +17 -0
- data/spec/components/number_spec.rb +64 -0
- data/spec/components/progress_spec.rb +23 -0
- data/spec/components/slider_spec.rb +25 -0
- data/spec/components/tabs_spec.rb +50 -0
- data/spec/components/textarea_spec.rb +7 -0
- data/spec/components/time_spec.rb +37 -0
- data/spec/components/title_spec.rb +11 -0
- data/spec/examples/comments_spec.rb +72 -0
- data/spec/examples/todos_spec.rb +39 -0
- data/spec/lib/collection_spec.rb +38 -0
- data/spec/lib/lorem_spec.rb +55 -0
- data/spec/lib/state_serializer_spec.rb +58 -0
- data/spec/lib/storage_spec.rb +39 -0
- data/spec/spec_helper.rb +1 -0
- metadata +223 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
module UI
|
2
|
+
# Checkbox component.
|
3
|
+
#
|
4
|
+
# Features:
|
5
|
+
# * checked and checked=
|
6
|
+
# * actionable
|
7
|
+
#
|
8
|
+
# @attr value [Boolean] The value
|
9
|
+
#
|
10
|
+
# @author Gusztáv Szikszai
|
11
|
+
# @since 0.1.0
|
12
|
+
class Checkbox < Action
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
tag 'ui-checkbox'
|
16
|
+
|
17
|
+
def_delegators :@input, :checked, :checked=
|
18
|
+
|
19
|
+
component :input, :input, tabindex: -1, type: :checkbox
|
20
|
+
component :label, :label, tabindex: -1 do
|
21
|
+
component :icon, UI::Icon, glyph: :checkmark
|
22
|
+
end
|
23
|
+
|
24
|
+
style height: -> { theme.size.em },
|
25
|
+
width: -> { theme.size.em },
|
26
|
+
display: 'inline-block',
|
27
|
+
input: { display: :none,
|
28
|
+
'&:checked + label' => { 'ui-icon' => { transform: 'scale(1)',
|
29
|
+
opacity: 1 } } },
|
30
|
+
label: { transition: 'opacity 320ms, transform 320ms',
|
31
|
+
borderRadius: -> { theme.border_radius.em },
|
32
|
+
color: -> { readable_color colors.input },
|
33
|
+
background: -> { colors.input },
|
34
|
+
justifyContent: :center,
|
35
|
+
alignItems: :center,
|
36
|
+
cursor: :pointer,
|
37
|
+
height: :inherit,
|
38
|
+
display: :flex,
|
39
|
+
width: :inherit,
|
40
|
+
'ui-icon' => { transform: 'scale(0.4) rotate(45deg)',
|
41
|
+
transition: :inherit,
|
42
|
+
fontSize: 1.em,
|
43
|
+
opacity: 0 } },
|
44
|
+
'&:focus label' => { boxShadow: -> { theme.focus_box_shadow.call } }
|
45
|
+
|
46
|
+
on :click, :action
|
47
|
+
|
48
|
+
# Toggles the checkbox
|
49
|
+
def action
|
50
|
+
self.checked = !checked
|
51
|
+
trigger :change
|
52
|
+
end
|
53
|
+
|
54
|
+
alias value= checked=
|
55
|
+
alias value checked
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
require 'fron-ui/components/dropdown'
|
2
|
+
require 'fron-ui/components/input'
|
3
|
+
|
4
|
+
module UI
|
5
|
+
# Chooser component
|
6
|
+
#
|
7
|
+
# Features
|
8
|
+
# * Select an item from the dropdown
|
9
|
+
# * Allows multiple selection (multiple attribute)
|
10
|
+
# * Search the items from the input
|
11
|
+
# * Navigate / select with keyboard
|
12
|
+
#
|
13
|
+
# @author Gusztáv Szikszai
|
14
|
+
# @since 0.1.0
|
15
|
+
class Chooser < Base
|
16
|
+
# Item for the chooser
|
17
|
+
class Item < Fron::Component
|
18
|
+
include ::Record
|
19
|
+
|
20
|
+
tag 'ui-chooser-item'
|
21
|
+
|
22
|
+
style display: :block,
|
23
|
+
padding: -> { (theme.spacing / 2).em }
|
24
|
+
|
25
|
+
# Renders the item
|
26
|
+
def render
|
27
|
+
self.text = label
|
28
|
+
end
|
29
|
+
|
30
|
+
def label
|
31
|
+
@data[:value]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Retunrs the value
|
35
|
+
#
|
36
|
+
# @return [String] The value
|
37
|
+
def value
|
38
|
+
text
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# List for the chooser
|
43
|
+
class List < Collection
|
44
|
+
include UI::Behaviors::SelectableChildren
|
45
|
+
include UI::Behaviors::IntendableChildren
|
46
|
+
|
47
|
+
tag 'ui-chooser-list'
|
48
|
+
|
49
|
+
style background: -> { colors.input },
|
50
|
+
borderRadius: -> { theme.border_radius.em },
|
51
|
+
color: -> { readable_color colors.input },
|
52
|
+
overflow: :auto,
|
53
|
+
'> *.selected' => { background: -> { colors.primary },
|
54
|
+
color: -> { readable_color colors.primary },
|
55
|
+
'&:first-child' => { borderTopLeftRadius: :inherit,
|
56
|
+
borderTopRightRadius: :inherit },
|
57
|
+
'&:last-child' => { borderBottomLeftRadius: :inherit,
|
58
|
+
borderBottomRightRadius: :inherit } }
|
59
|
+
|
60
|
+
# Selects the currently intended item
|
61
|
+
def select_intended
|
62
|
+
select intended
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
include UI::Behaviors::Dropdown
|
67
|
+
include UI::Behaviors::Keydown
|
68
|
+
extend Forwardable
|
69
|
+
|
70
|
+
tag 'ui-chooser'
|
71
|
+
|
72
|
+
component :input, UI::Input
|
73
|
+
component :dropdown, UI::Dropdown do
|
74
|
+
component :list, List, base: Item, deselectable: true
|
75
|
+
end
|
76
|
+
|
77
|
+
dropdown :input, :dropdown
|
78
|
+
|
79
|
+
def_delegators :dropdown, :list
|
80
|
+
def_delegators :input, :placeholder, :placeholder=, :blur, :active?, :focus
|
81
|
+
def_delegators :list, :base, :base=, :key, :key=, :multiple, :multiple=,
|
82
|
+
:intend_next, :intend_previous, :select_intended, :items,
|
83
|
+
:select_first, :select_last, :deselectable, :deselectable=
|
84
|
+
|
85
|
+
style position: :relative,
|
86
|
+
color: -> { readable_color colors.input },
|
87
|
+
input: { cursor: :pointer,
|
88
|
+
width: '100%' },
|
89
|
+
'ui-dropdown' => { left: 0,
|
90
|
+
right: 0,
|
91
|
+
maxHeight: 300.px,
|
92
|
+
overflowY: :auto },
|
93
|
+
'&:not([focused]):after,
|
94
|
+
&:not([searchable]):after' => { background: -> { "linear-gradient(90deg, #{rgba(colors.input, 0)}, #{colors.input} 70%)" },
|
95
|
+
borderBottomRightRadius: :inherit,
|
96
|
+
borderTopRightRadius: :inherit,
|
97
|
+
pointerEvents: :none,
|
98
|
+
position: :absolute,
|
99
|
+
content: '""',
|
100
|
+
width: 4.em,
|
101
|
+
bottom: 0.15.em,
|
102
|
+
right: 0.15.em,
|
103
|
+
top: 0.15.em },
|
104
|
+
'&:before' => { borderStyle: :solid,
|
105
|
+
position: :absolute,
|
106
|
+
marginTop: -0.2.em,
|
107
|
+
content: "''",
|
108
|
+
opacity: 0.5,
|
109
|
+
top: '50%',
|
110
|
+
height: 0,
|
111
|
+
pointerEvents: :none,
|
112
|
+
width: 0,
|
113
|
+
borderColor: 'currentColor transparent transparent transparent',
|
114
|
+
borderWidth: '0.4em 0.35em 0 0.35em',
|
115
|
+
right: 0.75.em,
|
116
|
+
zIndex: 1 }
|
117
|
+
|
118
|
+
keydown :esc, :blur
|
119
|
+
keydown :up, :intend_previous
|
120
|
+
keydown :down, :intend_next
|
121
|
+
|
122
|
+
on :input, :filter
|
123
|
+
on :selected_change, :selected_changed
|
124
|
+
on :keydown, :keydown
|
125
|
+
|
126
|
+
# Initilaizes the component, setting
|
127
|
+
# up not bubbling events on the input
|
128
|
+
def initialize
|
129
|
+
super
|
130
|
+
@dropdown.on :mousedown, &:prevent_default
|
131
|
+
|
132
|
+
self.searchable = true
|
133
|
+
@input.on(:focus) do
|
134
|
+
self[:focused] = ''
|
135
|
+
show_items
|
136
|
+
empty_input
|
137
|
+
end
|
138
|
+
@input.on(:blur) do
|
139
|
+
remove_attribute :focused
|
140
|
+
update_input
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def keydown(event)
|
145
|
+
return unless [:space, :enter].include?(event.key)
|
146
|
+
result = select_intended
|
147
|
+
event.stop if result
|
148
|
+
end
|
149
|
+
|
150
|
+
# Sets the searchable value, by setting
|
151
|
+
# the inputs to read only attribute.
|
152
|
+
#
|
153
|
+
# @param value [Boolean] The value
|
154
|
+
def searchable=(value)
|
155
|
+
@input.readonly = !value
|
156
|
+
if @input.readonly
|
157
|
+
remove_attribute :searchable
|
158
|
+
else
|
159
|
+
self[:searchable] = ''
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Returns if the component is searchable or not
|
164
|
+
#
|
165
|
+
# @return [Boolean] The value
|
166
|
+
def searchable
|
167
|
+
!@input.readonly
|
168
|
+
end
|
169
|
+
|
170
|
+
# Sets the items
|
171
|
+
#
|
172
|
+
# @param items [Array<Hash>] The items
|
173
|
+
def items=(items)
|
174
|
+
items = items.each_with_index.map { |item, index| { id: index, value: item } } unless items[0].is_a?(Hash)
|
175
|
+
list.items = items
|
176
|
+
update_input
|
177
|
+
end
|
178
|
+
|
179
|
+
# Empties the input
|
180
|
+
def empty_input
|
181
|
+
return if @input.readonly
|
182
|
+
@input.value = ''
|
183
|
+
end
|
184
|
+
|
185
|
+
# Updates the input after
|
186
|
+
# an item has been selected
|
187
|
+
def selected_changed
|
188
|
+
return if searchable
|
189
|
+
update_input
|
190
|
+
trigger :change
|
191
|
+
@input.blur unless multiple
|
192
|
+
end
|
193
|
+
|
194
|
+
# Updates the input text with
|
195
|
+
# the selected items values
|
196
|
+
def update_input
|
197
|
+
timeout(320) { show_items }
|
198
|
+
@input.value = Array(label).join(', ')
|
199
|
+
end
|
200
|
+
|
201
|
+
# Shows the items
|
202
|
+
def show_items
|
203
|
+
list.children.each { |item| item.remove_class :hidden }
|
204
|
+
end
|
205
|
+
|
206
|
+
# Filters the items by the input label
|
207
|
+
def filter
|
208
|
+
list.children.each do |item|
|
209
|
+
item.toggle_class :hidden, !(item.label =~ Regexp.new(@input.value, 'i'))
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Returns the value
|
214
|
+
#
|
215
|
+
# @return The value
|
216
|
+
def value
|
217
|
+
selected_items(:value)
|
218
|
+
end
|
219
|
+
|
220
|
+
def selected_items(property)
|
221
|
+
selected = list.selected
|
222
|
+
return nil unless selected
|
223
|
+
if selected.is_a?(Array)
|
224
|
+
return nil if selected.empty?
|
225
|
+
selected.map { |item| item.send(property) }
|
226
|
+
else
|
227
|
+
selected.send(property)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def label
|
232
|
+
selected_items(:label)
|
233
|
+
end
|
234
|
+
|
235
|
+
def value=(new_value)
|
236
|
+
return if attribute?(:focused)
|
237
|
+
values = Array(new_value)
|
238
|
+
return if Array(value) == values
|
239
|
+
list.deselect
|
240
|
+
values.each do |val|
|
241
|
+
list.select list.children.find { |child| child.value == val }
|
242
|
+
end
|
243
|
+
update_input
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require 'fron-ui/components/container'
|
2
|
+
require 'fron-ui/components/drag'
|
3
|
+
|
4
|
+
require 'native'
|
5
|
+
|
6
|
+
module Color
|
7
|
+
class RGB
|
8
|
+
def css_rgba(alpha)
|
9
|
+
alpha = format('%.1f', alpha)
|
10
|
+
"rgba(#{red.round},#{green.round},#{blue.round},#{alpha})"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module UI
|
16
|
+
# Color selector component with a draggable field for
|
17
|
+
# selecting the saturation / value and
|
18
|
+
# a slider for selecting hue.
|
19
|
+
#
|
20
|
+
# @attr_reader [Integer] hue The hue of the represented color
|
21
|
+
# @attr_reader [Integer] value The value of the represented color
|
22
|
+
# @attr_reader [Integer] saturation The saturation of the represented color
|
23
|
+
#
|
24
|
+
# @author Gusztáv Szikszai
|
25
|
+
# @since 0.1.0
|
26
|
+
class ColorPanel < UI::Container
|
27
|
+
extend Forwardable
|
28
|
+
|
29
|
+
CHECKER = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wkUCDAEK+8FsQAAADNJREFUKM9jPHPmDAM2YGxsjFWciYFEMKqBGMD4//9/rBJnz54d8p7G5TecGhgZGQfIDwBAQAuAAa2FpwAAAABJRU5ErkJggg=='
|
30
|
+
|
31
|
+
tag 'ui-color-wheel'
|
32
|
+
|
33
|
+
defaults direction: :column
|
34
|
+
|
35
|
+
style borderRadius: -> { theme.border_radius.em },
|
36
|
+
padding: -> { (theme.spacing * 2).em },
|
37
|
+
background: -> { colors.input },
|
38
|
+
'ui-drag' => { boxShadow: '0 0 1px 1px rgba(0,0,0,0.2) inset',
|
39
|
+
cursor: :pointer },
|
40
|
+
'> ui-drag' => {
|
41
|
+
backgroundPosition: :center,
|
42
|
+
height: 1.2.em,
|
43
|
+
'ui-drag-handle' => { transform: 'translateX(-50%)',
|
44
|
+
borderRadius: 0.5.em,
|
45
|
+
top: -0.25.em,
|
46
|
+
bottom: -0.25.em,
|
47
|
+
width: 0.5.em,
|
48
|
+
height: 'auto' }
|
49
|
+
},
|
50
|
+
'ui-container' => {
|
51
|
+
'ui-drag:first-of-type' => { backgroundImage: 'linear-gradient(0deg, #000 0%, transparent 100%),
|
52
|
+
linear-gradient(90deg, #fff 0%, rgba(255,255,255,0) 100%)',
|
53
|
+
width: 14.em,
|
54
|
+
height: 14.em },
|
55
|
+
'ui-drag:last-of-type' => { backgroundImage: 'linear-gradient(to bottom, #ff0000 0%, #ffff00 17%, #00ff00 33%,
|
56
|
+
#00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%)',
|
57
|
+
width: 1.2.em,
|
58
|
+
'ui-drag-handle' => { transform: 'translateY(-50%)',
|
59
|
+
borderRadius: 0.5.em,
|
60
|
+
right: -0.25.em,
|
61
|
+
left: -0.25.em,
|
62
|
+
height: 0.5.em,
|
63
|
+
width: 'auto' } }
|
64
|
+
}
|
65
|
+
|
66
|
+
component :container, UI::Container, direction: :row do
|
67
|
+
component :recta, UI::Drag
|
68
|
+
component :hued, UI::Drag, horizontal: false
|
69
|
+
end
|
70
|
+
component :alphad, UI::Drag, vertical: false
|
71
|
+
|
72
|
+
on :change, 'ui-drag', :change
|
73
|
+
on :mousedown, :stop
|
74
|
+
|
75
|
+
def_delegators :container, :recta, :hued
|
76
|
+
|
77
|
+
attr_reader :hue, :saturation, :value
|
78
|
+
|
79
|
+
# Initializes the component with default
|
80
|
+
# values for hue, value and saturation
|
81
|
+
def initialize
|
82
|
+
super
|
83
|
+
|
84
|
+
@hue = 0
|
85
|
+
@value = 100
|
86
|
+
@saturation = 0
|
87
|
+
@alpha = 0
|
88
|
+
|
89
|
+
render
|
90
|
+
end
|
91
|
+
|
92
|
+
def stop(event)
|
93
|
+
event.stop
|
94
|
+
end
|
95
|
+
|
96
|
+
# Handles changes from the fields, sets the
|
97
|
+
# hue, value and saturation from on the fields.
|
98
|
+
#
|
99
|
+
# @param event [DOM::Event] The event
|
100
|
+
def change(event)
|
101
|
+
event.stop
|
102
|
+
hue = hued.value_y * 360
|
103
|
+
value = (1 - recta.value_y) * 100
|
104
|
+
saturation = recta.value_x * 100
|
105
|
+
alpha = @alphad.value_x * 100
|
106
|
+
return if hue == @hue &&
|
107
|
+
value == @value &&
|
108
|
+
saturation == @saturation &&
|
109
|
+
alpha == @alpha
|
110
|
+
@hue = hue
|
111
|
+
@value = value
|
112
|
+
@saturation = saturation
|
113
|
+
@alpha = alpha
|
114
|
+
render
|
115
|
+
trigger :change
|
116
|
+
end
|
117
|
+
|
118
|
+
# Sets the hue to the given value
|
119
|
+
#
|
120
|
+
# @param value [Integer] Integer between 0 and 360
|
121
|
+
def hue=(value)
|
122
|
+
@hue = value
|
123
|
+
hued.value_y = value / 360
|
124
|
+
render
|
125
|
+
end
|
126
|
+
|
127
|
+
# Sets the saturation to the given value
|
128
|
+
#
|
129
|
+
# @param value [Integer] Integer between 0 and 100
|
130
|
+
def saturation=(value)
|
131
|
+
@saturation = value
|
132
|
+
recta.value_x = value / 100
|
133
|
+
render
|
134
|
+
end
|
135
|
+
|
136
|
+
# Sets the value to the given value
|
137
|
+
#
|
138
|
+
# @param value [Integer] Integer between 0 and 100
|
139
|
+
def value=(value)
|
140
|
+
@value = value
|
141
|
+
recta.value_y = (100 - value) / 100
|
142
|
+
render
|
143
|
+
end
|
144
|
+
|
145
|
+
def alpha=(value)
|
146
|
+
@alpha = value
|
147
|
+
@alphad.value_x = value / 100
|
148
|
+
render
|
149
|
+
end
|
150
|
+
|
151
|
+
def to_css
|
152
|
+
if @alpha.round == 100
|
153
|
+
'#' + color.hex.upcase
|
154
|
+
else
|
155
|
+
color.css_rgba(@alpha / 100)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
# Returns the color
|
162
|
+
#
|
163
|
+
# :reek:UncommunicativeVariableName
|
164
|
+
#
|
165
|
+
# @return [Color::RGB] The color
|
166
|
+
def color
|
167
|
+
s = saturation / 100
|
168
|
+
v = value / 100
|
169
|
+
h = hue
|
170
|
+
|
171
|
+
c = v * s
|
172
|
+
x = c * (1 - ((h / 60).modulo(2) - 1).abs)
|
173
|
+
m = v - c
|
174
|
+
|
175
|
+
r, g, b = if (0...60).cover?(h)
|
176
|
+
[c, x, 0]
|
177
|
+
elsif (60...120).cover?(h)
|
178
|
+
[x, c, 0]
|
179
|
+
elsif (120...180).cover?(h)
|
180
|
+
[0, c, x]
|
181
|
+
elsif (180...240).cover?(h)
|
182
|
+
[0, x, c]
|
183
|
+
elsif (240...300).cover?(h)
|
184
|
+
[x, 0, c]
|
185
|
+
elsif (300..360).cover?(h)
|
186
|
+
[c, 0, x]
|
187
|
+
end
|
188
|
+
|
189
|
+
Color::RGB.from_fraction(r + m, g + m, b + m)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Sets the color from a string
|
193
|
+
#
|
194
|
+
# :reek:UncommunicativeVariableName
|
195
|
+
#
|
196
|
+
# @param color [String] The hex or CSS representation
|
197
|
+
def color=(color)
|
198
|
+
if (md = color.match(/rgba\((\d{1,3}),(\d{1,3}),(\d{1,3}),(\d+\.\d+)\)/))
|
199
|
+
color = Color::RGB.new(md[1].to_i, md[2].to_i, md[3].to_i)
|
200
|
+
alpha = md[4].to_f * 100
|
201
|
+
else
|
202
|
+
alpha = 100
|
203
|
+
end
|
204
|
+
color = Color::RGB.by_css(color) unless color.is_a? Color
|
205
|
+
rgb = color.to_rgb.to_a
|
206
|
+
r, g, b = rgb
|
207
|
+
|
208
|
+
cmax = rgb.max
|
209
|
+
cmin = rgb.min
|
210
|
+
|
211
|
+
delta = cmax - cmin
|
212
|
+
|
213
|
+
self.hue = if delta == 0
|
214
|
+
0
|
215
|
+
elsif cmax == r
|
216
|
+
60 * ((g - b) / delta).modulo(6)
|
217
|
+
elsif cmax == g
|
218
|
+
60 * (((b - r) / delta) + 2)
|
219
|
+
elsif cmax == b
|
220
|
+
60 * (((r - g) / delta) + 4)
|
221
|
+
end
|
222
|
+
|
223
|
+
self.saturation = (cmax == 0 ? 0 : delta / cmax) * 100
|
224
|
+
self.value = cmax * 100
|
225
|
+
self.alpha = alpha
|
226
|
+
end
|
227
|
+
|
228
|
+
# Renders the component
|
229
|
+
def render
|
230
|
+
rgb = "#{color.red.round}, #{color.green.round}, #{color.blue.round}"
|
231
|
+
alphad.style.background = "linear-gradient(90deg, rgba(#{rgb},0), rgba(#{rgb},1)), url(#{CHECKER})"
|
232
|
+
recta.style.backgroundColor = "hsl(#{hue}, 100%, 50%)"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|