fron-ui 1.0.0rc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,67 @@
|
|
|
1
|
+
module UI
|
|
2
|
+
module Behaviors
|
|
3
|
+
# Behavior for handling for selecting
|
|
4
|
+
# one of the children of the component.
|
|
5
|
+
#
|
|
6
|
+
# @author Gusztáv Szikszai
|
|
7
|
+
# @since 0.1.0
|
|
8
|
+
module SelectableChildren
|
|
9
|
+
# Sets up the behavior:
|
|
10
|
+
#
|
|
11
|
+
# * Sets up click event for the selecting
|
|
12
|
+
# * Sets styles for children to have pointer cursor
|
|
13
|
+
#
|
|
14
|
+
# @param base [Fron::Component] The includer
|
|
15
|
+
def self.included(base)
|
|
16
|
+
base.on :click, :on_select
|
|
17
|
+
base.attribute_accessor :multiple, default: false
|
|
18
|
+
base.attribute_accessor :deselectable, default: false
|
|
19
|
+
base.style '> *' => { cursor: :pointer }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Runs when a child is clicked
|
|
23
|
+
#
|
|
24
|
+
# @param event [DOM::Event] The event
|
|
25
|
+
def on_select(event)
|
|
26
|
+
select children.find { |child| child.include?(event.target) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Selects the given child
|
|
30
|
+
#
|
|
31
|
+
# @param child [Fron::Component] The child
|
|
32
|
+
def select(child)
|
|
33
|
+
return unless child
|
|
34
|
+
return if !deselectable && selected == child
|
|
35
|
+
deselect if !multiple && selected != child
|
|
36
|
+
child.toggle_class :selected
|
|
37
|
+
trigger :selected_change
|
|
38
|
+
true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Deselects all selected children
|
|
42
|
+
def deselect
|
|
43
|
+
Array(selected).each { |item| item.remove_class :selected }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Selects the first child
|
|
47
|
+
def select_first
|
|
48
|
+
select children.first
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Selects the last child
|
|
52
|
+
def select_last
|
|
53
|
+
select children.last
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns the selected child
|
|
57
|
+
#
|
|
58
|
+
# @return [Fron::Component] The child
|
|
59
|
+
def selected
|
|
60
|
+
selected = children.select { |child| child.has_class :selected }
|
|
61
|
+
return nil if selected.empty?
|
|
62
|
+
return selected.first if selected.count == 1
|
|
63
|
+
selected
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module UI
|
|
2
|
+
module Behaviors
|
|
3
|
+
# Behavior for seralizing and
|
|
4
|
+
# deseralizing data from named
|
|
5
|
+
# (having a name attribute) components
|
|
6
|
+
#
|
|
7
|
+
# @author Gusztáv Szikszai
|
|
8
|
+
# @since 0.1.0
|
|
9
|
+
module Serialize
|
|
10
|
+
# Populates items with the given data
|
|
11
|
+
#
|
|
12
|
+
# @param data [Hash] The data
|
|
13
|
+
def load(data)
|
|
14
|
+
@data = data
|
|
15
|
+
find_all('[name]').each do |item|
|
|
16
|
+
item.value = @data[item[:name]]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Gathers the data from elements
|
|
21
|
+
#
|
|
22
|
+
# :reek:FeatureEnvy
|
|
23
|
+
#
|
|
24
|
+
# @return [Hash] The data
|
|
25
|
+
def data
|
|
26
|
+
find_all('[name]').each_with_object(@data || {}) do |item, memo|
|
|
27
|
+
memo[item[:name]] = item.value
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module UI
|
|
2
|
+
module Behaviors
|
|
3
|
+
module Shortcuts
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.register self, [:shortcut]
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.shortcut(item)
|
|
9
|
+
initialize_shortcuts
|
|
10
|
+
shortcut, action = item[:args]
|
|
11
|
+
shortcuts << { parts: shortcut.split(Fron::Keyboard::DELIMETERS), action: action }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize_shortcuts
|
|
15
|
+
@shortcut_event = DOM::Window.on(:keyup) do |event|
|
|
16
|
+
break if DOM::Document.active_element
|
|
17
|
+
break unless visible?
|
|
18
|
+
|
|
19
|
+
combo = Fron::Keyboard.calculate_shortcut event
|
|
20
|
+
|
|
21
|
+
shortcuts.each do |shortcut|
|
|
22
|
+
next unless shortcut[:parts].sort == combo.sort
|
|
23
|
+
send shortcut[:action]
|
|
24
|
+
event.stop
|
|
25
|
+
break
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def shortcuts
|
|
31
|
+
@shortcuts ||= []
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module UI
|
|
2
|
+
module Behaviors
|
|
3
|
+
# Behavior for managing state into
|
|
4
|
+
# search part of the location.
|
|
5
|
+
#
|
|
6
|
+
# @author Gusztáv Szikszai
|
|
7
|
+
# @since 0.1.0
|
|
8
|
+
module State
|
|
9
|
+
# Sets up the behavior.
|
|
10
|
+
#
|
|
11
|
+
# @param base [Fron::Component] The includer
|
|
12
|
+
def self.included(base)
|
|
13
|
+
base.register self, [:state_changed]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Sets up events for popstate to handle
|
|
17
|
+
# back and forward buttons.
|
|
18
|
+
#
|
|
19
|
+
# @param item [Hash] The arguments
|
|
20
|
+
def self.state_changed(item)
|
|
21
|
+
method = item[:args][0]
|
|
22
|
+
DOM::Window.on('popstate') { send method }
|
|
23
|
+
timeout do
|
|
24
|
+
send method
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the decoded state.
|
|
29
|
+
#
|
|
30
|
+
# @return The state
|
|
31
|
+
def state
|
|
32
|
+
search = `location.search`[1..-1]
|
|
33
|
+
StateSerializer.decode(`window.atob(#{search})`)
|
|
34
|
+
rescue
|
|
35
|
+
''
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Sets the state
|
|
39
|
+
#
|
|
40
|
+
# @param new_state The new state
|
|
41
|
+
def state=(new_state, options = {})
|
|
42
|
+
return if state == new_state
|
|
43
|
+
path = yield if block_given?
|
|
44
|
+
hash = `window.btoa(#{StateSerializer.encode(new_state)})`
|
|
45
|
+
new_url = path.to_s + '?' + hash
|
|
46
|
+
if options.to_h[:replace]
|
|
47
|
+
DOM::Window.replace_state new_url
|
|
48
|
+
else
|
|
49
|
+
DOM::Window.state = new_url
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
alias set_state state=
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module UI
|
|
2
|
+
module Behaviors
|
|
3
|
+
# Behavior for managing style transitions.
|
|
4
|
+
#
|
|
5
|
+
# @author Gusztáv Szikszai
|
|
6
|
+
# @since 0.1.0
|
|
7
|
+
module Transition
|
|
8
|
+
# Sets up the behavior:
|
|
9
|
+
#
|
|
10
|
+
# * Listens on animation end events (prefixed)
|
|
11
|
+
#
|
|
12
|
+
# @param base [Fron::Component] The includer
|
|
13
|
+
def self.included(base)
|
|
14
|
+
base.on :animationend, :on_transition_end
|
|
15
|
+
base.on :webkitAnimationEnd, :on_transition_end
|
|
16
|
+
base.on :oanimationend, :on_transition_end
|
|
17
|
+
base.on :MSAnimationEnd, :on_transition_end
|
|
18
|
+
base.meta_def :transition do |animation_name, options|
|
|
19
|
+
name = "#{tagname}-#{animation_name}"
|
|
20
|
+
options[:animation_name] = name
|
|
21
|
+
Fron::Sheet.add_animation name, options[:frames]
|
|
22
|
+
@registry << { method: Transition.method(:init_transition),
|
|
23
|
+
args: [animation_name, options],
|
|
24
|
+
id: SecureRandom.uuid }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Handles animation end events, and
|
|
29
|
+
# calls the callback if sepcified.
|
|
30
|
+
#
|
|
31
|
+
# @param event [DOM::Event] The event
|
|
32
|
+
def on_transition_end(event)
|
|
33
|
+
options = @transitions[event.animationName.split('-').last]
|
|
34
|
+
send options[:callback] if options &&
|
|
35
|
+
options[:callback] &&
|
|
36
|
+
respond_to?(options[:callback])
|
|
37
|
+
@transition_callback.call if @transition_callback
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Adds the transitions animations to
|
|
41
|
+
# the stylesheet.
|
|
42
|
+
#
|
|
43
|
+
# @param item [Hash] The item data
|
|
44
|
+
def self.init_transition(item)
|
|
45
|
+
name = item[:args][0]
|
|
46
|
+
options = item[:args][1]
|
|
47
|
+
@transitions ||= {}
|
|
48
|
+
@transitions[name] = options
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Starts the transition with the given name.
|
|
52
|
+
#
|
|
53
|
+
# @param name [String] The name of the transition
|
|
54
|
+
def transition!(name, &block)
|
|
55
|
+
options = @transitions[name]
|
|
56
|
+
@style.animation = [options[:animation_name],
|
|
57
|
+
options[:duration],
|
|
58
|
+
options[:delay], 'both'].join(' ')
|
|
59
|
+
@transition_callback = block
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module UI
|
|
2
|
+
# Base component for elements that handle
|
|
3
|
+
# *action* (enter or space) as well as clicks.
|
|
4
|
+
#
|
|
5
|
+
# @author Gusztáv Szikszai
|
|
6
|
+
# @since 0.1.0
|
|
7
|
+
class Action < Base
|
|
8
|
+
include UI::Behaviors::Action
|
|
9
|
+
|
|
10
|
+
tag 'ui-action'
|
|
11
|
+
|
|
12
|
+
# Initializes the component by setting the tabindex.
|
|
13
|
+
def initialize
|
|
14
|
+
super
|
|
15
|
+
self[:tabindex] ||= 0
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'fron-ui/components/container'
|
|
2
|
+
|
|
3
|
+
module UI
|
|
4
|
+
# Box component with background, padding
|
|
5
|
+
# and border radius
|
|
6
|
+
#
|
|
7
|
+
# @author Gusztáv Szikszai
|
|
8
|
+
# @since 0.1.0
|
|
9
|
+
class Box < Container
|
|
10
|
+
tag 'ui-box'
|
|
11
|
+
|
|
12
|
+
style borderRadius: -> { (theme.border_radius * 2).em },
|
|
13
|
+
color: -> { readable_color colors.background },
|
|
14
|
+
padding: -> { (theme.spacing * 1.75).em },
|
|
15
|
+
background: -> { colors.background }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require 'fron-ui/components/action'
|
|
2
|
+
|
|
3
|
+
module UI
|
|
4
|
+
# Button component.
|
|
5
|
+
#
|
|
6
|
+
# It has the following types:
|
|
7
|
+
# * primary
|
|
8
|
+
# * success
|
|
9
|
+
# * danger
|
|
10
|
+
#
|
|
11
|
+
# @author Gusztáv Szikszai
|
|
12
|
+
# @since 0.1.0
|
|
13
|
+
class Button < Action
|
|
14
|
+
tag 'ui-button'
|
|
15
|
+
|
|
16
|
+
style padding: -> { "0 #{(theme.spacing * 1.4).em}" },
|
|
17
|
+
borderRadius: -> { theme.border_radius.em },
|
|
18
|
+
fontFamily: -> { theme.font_family },
|
|
19
|
+
lineHeight: -> { theme.size.em },
|
|
20
|
+
height: -> { theme.size.em },
|
|
21
|
+
textOverflow: :ellipsis,
|
|
22
|
+
display: 'inline-block',
|
|
23
|
+
whiteSpace: :nowrap,
|
|
24
|
+
textAlign: :center,
|
|
25
|
+
userSelect: :none,
|
|
26
|
+
fontWeight: '600',
|
|
27
|
+
overflow: :hidden,
|
|
28
|
+
'&[type]:focus' => { boxShadow: -> { theme.focus_box_shadow.call } },
|
|
29
|
+
'&[shape=square]' => { minWidth: -> { theme.size.em },
|
|
30
|
+
height: -> { theme.size.em },
|
|
31
|
+
justifyContent: :center,
|
|
32
|
+
display: 'inline-flex',
|
|
33
|
+
alignItems: :center,
|
|
34
|
+
padding: '0' },
|
|
35
|
+
'> *' => { pointerEvents: :none }
|
|
36
|
+
|
|
37
|
+
Fron::Sheet.helper.colors.to_h.keys.each do |type|
|
|
38
|
+
style "&[type=#{type}]" => { color: -> { readable_color(colors[type]) },
|
|
39
|
+
background: -> { colors[type] } }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Initializes the button by setting
|
|
43
|
+
# the type to primary.
|
|
44
|
+
def initialize
|
|
45
|
+
super
|
|
46
|
+
self[:type] ||= :primary
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def loading!(text = '')
|
|
50
|
+
@_text = self.text
|
|
51
|
+
self.text = text
|
|
52
|
+
self.disabled = true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def reset!
|
|
56
|
+
self.disabled = false
|
|
57
|
+
self.text = @_text
|
|
58
|
+
@_text = nil
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
module UI
|
|
2
|
+
# A component for rendering a month calendar for a given date.
|
|
3
|
+
#
|
|
4
|
+
# It has the following features:
|
|
5
|
+
# * Navigate to next / previous month
|
|
6
|
+
# * Yields all the days (with their cells) for extended usage
|
|
7
|
+
# * Displays the name of the rendered month (and year if it's not the same)
|
|
8
|
+
#
|
|
9
|
+
# @author Gusztáv Szikszai
|
|
10
|
+
# @since 0.1.0
|
|
11
|
+
class Calendar < Base
|
|
12
|
+
include UI::Behaviors::Actions
|
|
13
|
+
extend Forwardable
|
|
14
|
+
|
|
15
|
+
tag 'ui-calendar'
|
|
16
|
+
|
|
17
|
+
component :header, UI::Container, direction: :row do
|
|
18
|
+
component :icon, UI::Icon, glyph: 'arrow-left-b', clickable: true, action: :prev_month
|
|
19
|
+
component :label, UI::Label, flex: 1
|
|
20
|
+
component :icon, UI::Icon, glyph: 'arrow-right-b', clickable: true, action: :next_month
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
component :table, :table do
|
|
24
|
+
component :thead, :thead do
|
|
25
|
+
component :tr, :tr
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
component :tbody, :tbody
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def_delegators :table, :tbody, :thead
|
|
32
|
+
def_delegators :header, :label
|
|
33
|
+
|
|
34
|
+
style color: -> { readable_color colors.input },
|
|
35
|
+
borderRadius: -> { theme.border_radius.em },
|
|
36
|
+
padding: -> { (theme.spacing * 1.25).em },
|
|
37
|
+
background: -> { colors.input },
|
|
38
|
+
display: :block,
|
|
39
|
+
minWidth: 18.em,
|
|
40
|
+
'ui-label' => { textAlign: :center,
|
|
41
|
+
fontWeight: 700 },
|
|
42
|
+
'ui-icon' => { width: -> { (theme.spacing * 2).em } },
|
|
43
|
+
'ui-container' => { borderBottomWidth: -> { (theme.border_size / 2).em },
|
|
44
|
+
borderBottomColor: -> { dampen colors.input, 0.05 },
|
|
45
|
+
paddingBottom: -> { theme.spacing.em },
|
|
46
|
+
borderBottomStyle: :solid },
|
|
47
|
+
table: { borderSpacing: 0.4.em,
|
|
48
|
+
'td[date]' => { background: -> { dampen colors.input, 0.05 },
|
|
49
|
+
color: -> { readable_color(dampen(colors.input, 0.05)) } },
|
|
50
|
+
'th' => { fontSize: 0.8.em,
|
|
51
|
+
padding: -> { "#{theme.spacing.em} 0" },
|
|
52
|
+
opacity: 0.6 },
|
|
53
|
+
'td, th' => { borderRadius: -> { theme.border_radius.em },
|
|
54
|
+
textAlign: :center,
|
|
55
|
+
height: 2.em,
|
|
56
|
+
width: 2.em } }
|
|
57
|
+
|
|
58
|
+
# Initializes the calendar with todays date
|
|
59
|
+
def initialize
|
|
60
|
+
super
|
|
61
|
+
render
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Renders the next month in the calendar
|
|
65
|
+
def next_month
|
|
66
|
+
render @date.next_month
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Renders the previous month in the calendar
|
|
70
|
+
def prev_month
|
|
71
|
+
render @date.prev_month
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Renders the calendar for the given date
|
|
75
|
+
#
|
|
76
|
+
# @param date [Date] The date
|
|
77
|
+
def render(date = Date.today)
|
|
78
|
+
@date = date
|
|
79
|
+
label.text = date.strftime date.year == Date.today.year ? '%B' : '%B %Y'
|
|
80
|
+
render_header
|
|
81
|
+
render_body(date) do |*args|
|
|
82
|
+
yield(*args) if block_given?
|
|
83
|
+
end
|
|
84
|
+
trigger :rendered
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
# Renders the table header with the shortnames
|
|
90
|
+
def render_header
|
|
91
|
+
thead.tr.empty
|
|
92
|
+
Date.this_week.each do |day|
|
|
93
|
+
thead.tr << DOM::Element.new("th #{day.strftime('%a')}")
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Renders the table body with the days
|
|
98
|
+
def render_body
|
|
99
|
+
tbody.empty
|
|
100
|
+
|
|
101
|
+
@date.days_for_calendar.each_slice(7) do |data|
|
|
102
|
+
row = DOM::Element.new('tr')
|
|
103
|
+
row >> tbody
|
|
104
|
+
|
|
105
|
+
data.each do |day|
|
|
106
|
+
td = Td.new(day)
|
|
107
|
+
td >> row
|
|
108
|
+
next if day.is_a?(String)
|
|
109
|
+
yield day, td
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Cell for the calendar body
|
|
115
|
+
class Td < Fron::Component
|
|
116
|
+
tag 'td'
|
|
117
|
+
|
|
118
|
+
# Initialize the cell with the day
|
|
119
|
+
#
|
|
120
|
+
# @param day [Date] The day
|
|
121
|
+
def initialize(day)
|
|
122
|
+
super nil
|
|
123
|
+
return if day.is_a?(String)
|
|
124
|
+
self[:date] = day
|
|
125
|
+
self.text = day.day
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|