fron-ui 1.0.0rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.rubocop.yml +38 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +7 -0
  7. data/.yardopts +8 -0
  8. data/Gemfile +8 -0
  9. data/Gemfile.lock +105 -0
  10. data/Rakefile +37 -0
  11. data/Readme.md +4 -0
  12. data/db.json +192 -0
  13. data/fron-ui.gemspec +21 -0
  14. data/lib/fron-ui.rb +1 -0
  15. data/lib/fron_ui.rb +5 -0
  16. data/lib/fron_ui/version.rb +7 -0
  17. data/opal/fron-ui/base.rb +49 -0
  18. data/opal/fron-ui/behaviors/action.rb +40 -0
  19. data/opal/fron-ui/behaviors/actions.rb +40 -0
  20. data/opal/fron-ui/behaviors/confirmation.rb +23 -0
  21. data/opal/fron-ui/behaviors/dropdown.rb +27 -0
  22. data/opal/fron-ui/behaviors/file.rb +48 -0
  23. data/opal/fron-ui/behaviors/intendable_children.rb +76 -0
  24. data/opal/fron-ui/behaviors/keydown.rb +31 -0
  25. data/opal/fron-ui/behaviors/loop.rb +41 -0
  26. data/opal/fron-ui/behaviors/render.rb +30 -0
  27. data/opal/fron-ui/behaviors/rest.rb +121 -0
  28. data/opal/fron-ui/behaviors/selectable_children.rb +67 -0
  29. data/opal/fron-ui/behaviors/serialize.rb +32 -0
  30. data/opal/fron-ui/behaviors/shortcuts.rb +35 -0
  31. data/opal/fron-ui/behaviors/state.rb +56 -0
  32. data/opal/fron-ui/behaviors/transition.rb +63 -0
  33. data/opal/fron-ui/components/action.rb +18 -0
  34. data/opal/fron-ui/components/box.rb +17 -0
  35. data/opal/fron-ui/components/button.rb +61 -0
  36. data/opal/fron-ui/components/calendar.rb +129 -0
  37. data/opal/fron-ui/components/checkbox.rb +57 -0
  38. data/opal/fron-ui/components/chooser.rb +246 -0
  39. data/opal/fron-ui/components/color_panel.rb +235 -0
  40. data/opal/fron-ui/components/color_picker.rb +111 -0
  41. data/opal/fron-ui/components/container.rb +61 -0
  42. data/opal/fron-ui/components/date_picker.rb +141 -0
  43. data/opal/fron-ui/components/drag.rb +76 -0
  44. data/opal/fron-ui/components/dropdown.rb +72 -0
  45. data/opal/fron-ui/components/icon.rb +29 -0
  46. data/opal/fron-ui/components/image.rb +77 -0
  47. data/opal/fron-ui/components/input.rb +30 -0
  48. data/opal/fron-ui/components/label.rb +9 -0
  49. data/opal/fron-ui/components/list.rb +34 -0
  50. data/opal/fron-ui/components/loader.rb +63 -0
  51. data/opal/fron-ui/components/modal.rb +0 -0
  52. data/opal/fron-ui/components/notifications.rb +73 -0
  53. data/opal/fron-ui/components/number.rb +202 -0
  54. data/opal/fron-ui/components/progress.rb +52 -0
  55. data/opal/fron-ui/components/slider.rb +47 -0
  56. data/opal/fron-ui/components/tabs.rb +149 -0
  57. data/opal/fron-ui/components/textarea.rb +13 -0
  58. data/opal/fron-ui/components/time.rb +65 -0
  59. data/opal/fron-ui/components/title.rb +34 -0
  60. data/opal/fron-ui/examples/blog/index.rb +289 -0
  61. data/opal/fron-ui/examples/comments/components/comment.rb +75 -0
  62. data/opal/fron-ui/examples/comments/components/comments.rb +93 -0
  63. data/opal/fron-ui/examples/comments/components/footer.rb +36 -0
  64. data/opal/fron-ui/examples/comments/components/header.rb +35 -0
  65. data/opal/fron-ui/examples/comments/components/list.rb +12 -0
  66. data/opal/fron-ui/examples/comments/index.rb +6 -0
  67. data/opal/fron-ui/examples/contacts/components/contacts.rb +100 -0
  68. data/opal/fron-ui/examples/contacts/components/details.rb +92 -0
  69. data/opal/fron-ui/examples/contacts/components/item.rb +46 -0
  70. data/opal/fron-ui/examples/contacts/components/list.rb +10 -0
  71. data/opal/fron-ui/examples/contacts/components/sidebar.rb +30 -0
  72. data/opal/fron-ui/examples/contacts/index.rb +6 -0
  73. data/opal/fron-ui/examples/editor/index.rb +164 -0
  74. data/opal/fron-ui/examples/kitchensink/index.rb +193 -0
  75. data/opal/fron-ui/examples/todos/components/item.rb +84 -0
  76. data/opal/fron-ui/examples/todos/components/options.rb +26 -0
  77. data/opal/fron-ui/examples/todos/components/todos.rb +145 -0
  78. data/opal/fron-ui/examples/todos/index.rb +6 -0
  79. data/opal/fron-ui/examples/webshop/index.rb +0 -0
  80. data/opal/fron-ui/fonts/ionicons.rb +2954 -0
  81. data/opal/fron-ui/fonts/open_sans.rb +19 -0
  82. data/opal/fron-ui/lib/collection.rb +138 -0
  83. data/opal/fron-ui/lib/date.rb +23 -0
  84. data/opal/fron-ui/lib/debounce.rb +14 -0
  85. data/opal/fron-ui/lib/image_loader.rb +13 -0
  86. data/opal/fron-ui/lib/lorem.rb +93 -0
  87. data/opal/fron-ui/lib/nil.rb +29 -0
  88. data/opal/fron-ui/lib/record.rb +23 -0
  89. data/opal/fron-ui/lib/state_serializer.rb +129 -0
  90. data/opal/fron-ui/lib/storage.rb +57 -0
  91. data/opal/fron-ui/spec/setup.rb +40 -0
  92. data/opal/fron-ui/ui.rb +40 -0
  93. data/opal/fron-ui/utils/theme_roller.rb +63 -0
  94. data/opal/fron-ui/vendor/autoprefixer.js +21114 -0
  95. data/opal/fron-ui/vendor/marked.js +1291 -0
  96. data/opal/fron-ui/vendor/md5.js +274 -0
  97. data/opal/fron-ui/vendor/moment.js +3083 -0
  98. data/opal/fron-ui/vendor/uuid.js +92 -0
  99. data/opal/fron_ui.rb +13 -0
  100. data/spec/behaviors/action_spec.rb +34 -0
  101. data/spec/behaviors/actions_spec.rb +38 -0
  102. data/spec/behaviors/confirmation_spec.rb +23 -0
  103. data/spec/behaviors/dropdown_spec.rb +32 -0
  104. data/spec/behaviors/render_spec.rb +20 -0
  105. data/spec/behaviors/rest_spec.rb +70 -0
  106. data/spec/behaviors/selectable_children_spec.rb +40 -0
  107. data/spec/behaviors/serialize_spec.rb +34 -0
  108. data/spec/components/action_spec.rb +7 -0
  109. data/spec/components/base_spec.rb +19 -0
  110. data/spec/components/box_spec.rb +7 -0
  111. data/spec/components/button_spec.rb +9 -0
  112. data/spec/components/calendar_spec.rb +58 -0
  113. data/spec/components/checkbox_spec.rb +20 -0
  114. data/spec/components/chooser_spec.rb +75 -0
  115. data/spec/components/color_panel_spec.rb +49 -0
  116. data/spec/components/color_picker_spec.rb +41 -0
  117. data/spec/components/container_spec.rb +23 -0
  118. data/spec/components/date_picker_spec.rb +71 -0
  119. data/spec/components/drag_spec.rb +20 -0
  120. data/spec/components/dropdown_spec.rb +33 -0
  121. data/spec/components/image_spec.rb +33 -0
  122. data/spec/components/input_spec.rb +8 -0
  123. data/spec/components/list_spec.rb +10 -0
  124. data/spec/components/loader_spec.rb +9 -0
  125. data/spec/components/notifications_spec.rb +17 -0
  126. data/spec/components/number_spec.rb +64 -0
  127. data/spec/components/progress_spec.rb +23 -0
  128. data/spec/components/slider_spec.rb +25 -0
  129. data/spec/components/tabs_spec.rb +50 -0
  130. data/spec/components/textarea_spec.rb +7 -0
  131. data/spec/components/time_spec.rb +37 -0
  132. data/spec/components/title_spec.rb +11 -0
  133. data/spec/examples/comments_spec.rb +72 -0
  134. data/spec/examples/todos_spec.rb +39 -0
  135. data/spec/lib/collection_spec.rb +38 -0
  136. data/spec/lib/lorem_spec.rb +55 -0
  137. data/spec/lib/state_serializer_spec.rb +58 -0
  138. data/spec/lib/storage_spec.rb +39 -0
  139. data/spec/spec_helper.rb +1 -0
  140. metadata +223 -0
@@ -0,0 +1,46 @@
1
+ module Examples
2
+ class Contacts < UI::Container
3
+ # Item
4
+ class Item < UI::Container
5
+ include UI::Behaviors::Action
6
+ include ::Record
7
+
8
+ tag 'ui-item'
9
+
10
+ component :image, UI::Image, width: 2.em, height: 2.em
11
+ component :name, UI::Label
12
+
13
+ style padding: -> { (theme.spacing / 2).em },
14
+ lineHeight: 2.em,
15
+ transition: 'border 200ms',
16
+ borderLeft: '0em solid transparent',
17
+ 'ui-loader' => { fontSize: 0.6.em },
18
+ '&.selected' => {
19
+ borderLeft: -> { "0.4em solid #{colors.primary}" }
20
+ },
21
+ '&:focus' => {
22
+ borderLeft: -> { "0.4em solid #{colors.focus}" }
23
+ }
24
+
25
+ # Initializes the item by setting the direction
26
+ def initialize
27
+ super
28
+ self[:tabindex] = 0
29
+ self[:direction] = :row
30
+ end
31
+
32
+ # Returns the id of the item
33
+ #
34
+ # @return [String] The ID
35
+ def id
36
+ @data[:id]
37
+ end
38
+
39
+ # Renders the component
40
+ def render
41
+ @image.src = 'http://www.gravatar.com/avatar/' + `md5(#{data[:email] || ''})` + '?s=100&d=identicon'
42
+ @name.text = data[:name] || ' '
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,10 @@
1
+ module Examples
2
+ class Contacts < UI::Container
3
+ # List
4
+ class List < UI::List
5
+ include UI::Behaviors::SelectableChildren
6
+
7
+ tag 'ui-list'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,30 @@
1
+ require_relative 'item'
2
+ require_relative 'list'
3
+
4
+ module Examples
5
+ class Contacts < UI::Container
6
+ # Sidebar
7
+ class Sidebar < UI::Box
8
+ extend Forwardable
9
+
10
+ tag 'ui-sidebar'
11
+
12
+ component :title, UI::Title, text: 'Contacts'
13
+ component :input, UI::Input, placeholder: 'Search...'
14
+ component :list, List, empty_message: 'No contacts to display!', flex: 1, base: Item
15
+ component :button, UI::Button, text: 'Add new contact!', action: :add
16
+
17
+ def_delegators :list, :items, :items=, :selected, :deselect
18
+
19
+ ensure_styles!
20
+
21
+ # Selects the item with the given id
22
+ #
23
+ # @param id [String] The ID
24
+ def select(id)
25
+ return @list.deselect if id.empty?
26
+ @list.select @list.children.find { |item| item.data[:id] == id }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,6 @@
1
+ require 'fron_ui'
2
+ require_tree './components'
3
+ contacts = Examples::Contacts.new
4
+ contacts >> DOM::Document.body
5
+ contacts.refresh
6
+ Fron::Sheet.render_style_tag
@@ -0,0 +1,164 @@
1
+ require 'fron_ui'
2
+
3
+ URL = 'http://localhost:3000/files'
4
+
5
+ class Item < UI::Container
6
+ include UI::Behaviors::Rest
7
+ include UI::Behaviors::Action
8
+ include ::Record
9
+
10
+ style padding: -> { theme.spacing.em },
11
+ '> span' => { fontWeight: 600 }
12
+
13
+ component :span, :span
14
+
15
+ def render
16
+ @span.text = @data[:filename]
17
+ end
18
+
19
+ def action
20
+ trigger :dblclick
21
+ end
22
+ end
23
+
24
+ class List < UI::List
25
+ include UI::Behaviors::Rest
26
+
27
+ rest url: URL
28
+
29
+ def refresh
30
+ request :get, '' do |items|
31
+ self.items = items
32
+ end
33
+ end
34
+ end
35
+
36
+ class FileTab < UI::Container
37
+ include UI::Behaviors::Rest
38
+ include ::Record
39
+
40
+ tag 'ui-file'
41
+
42
+ rest url: URL
43
+
44
+ component :textarea, UI::Textarea, spellcheck: false
45
+
46
+ style position: :relative,
47
+ textarea: { position: :absolute,
48
+ padding: -> { theme.spacing.em },
49
+ width: '100%',
50
+ height: '100%',
51
+ resize: :none }
52
+
53
+ on :change, :save
54
+
55
+ def render
56
+ self[:tab_id] = @data[:id]
57
+ self[:tab_title] = @data[:filename]
58
+ @textarea.value = @data[:content].to_s
59
+ end
60
+
61
+ def save
62
+ update content: @textarea.value
63
+ end
64
+ end
65
+
66
+ class Handle < UI::Tabs::Handle
67
+ include UI::Behaviors::Actions
68
+ include ::Record
69
+
70
+ tag 'ui-tab-handle'
71
+
72
+ component :span, UI::Label, flex: 1
73
+ component :action, UI::Action, action: :close do
74
+ component :icon, UI::Icon, glyph: 'android-close'
75
+ end
76
+
77
+ style display: 'inline-flex',
78
+ '> ui-action' => {
79
+ display: :flex,
80
+ justifyContent: :center,
81
+ alignItems: :center,
82
+ width: 1.em,
83
+ marginLeft: 1.em,
84
+ pointerEvents: :auto }
85
+
86
+ def close
87
+ trigger :close_tab
88
+ end
89
+
90
+ def render
91
+ self.tab_id = @data[:id]
92
+ @span.text = @data[:title]
93
+ end
94
+ end
95
+
96
+ class Main < UI::Container
97
+ include UI::Behaviors::Actions
98
+ include UI::Behaviors::State
99
+ include UI::Behaviors::Rest
100
+
101
+ component :container, UI::Container, direction: :row, flex: 1 do
102
+ component :sidebar, UI::Box do
103
+ component :title, UI::Title, text: 'Files', direction: :row do
104
+ component :button, UI::Button, type: :success, shape: :square, action: :add do
105
+ component :icon, UI::Icon, glyph: :plus
106
+ end
107
+ end
108
+ component :list, List, base: Item
109
+ end
110
+ component :content, UI::Box, flex: 1 do
111
+ component :tabs, UI::Tabs, flex: 1, compact: true, base: Handle
112
+ end
113
+ end
114
+
115
+ rest url: URL
116
+
117
+ style padding: -> { theme.spacing.em },
118
+ boxSizing: 'border-box',
119
+ height: '100vh',
120
+ 'ui-box:first-child' => {
121
+ flex: '0 0 10em'
122
+ }
123
+
124
+ on :click, 'item', :open
125
+ on :close_tab, :close
126
+
127
+ state_changed :state_changed
128
+
129
+ def state_changed
130
+ end
131
+
132
+ def close(event)
133
+ @container.content.tabs.find_by_id(event.target.tab_id).remove!
134
+ end
135
+
136
+ def refresh
137
+ @container.sidebar.list.refresh
138
+ end
139
+
140
+ def add
141
+ name = prompt 'Name:'
142
+ return if name.to_s.strip.empty?
143
+ create id: SecureRandom.uuid, filename: name do
144
+ refresh
145
+ end
146
+ end
147
+
148
+ def open(event)
149
+ request :get, event.target.data[:id] do |data|
150
+ current = @container.content.tabs.find_by_id(data[:id])
151
+ break @container.content.tabs.select current if current
152
+ tab = FileTab.new
153
+ tab.data = data
154
+ @container.content.tabs << tab
155
+ @container.content.tabs.select tab
156
+ end
157
+ end
158
+ end
159
+
160
+ main = Main.new
161
+ main.refresh
162
+
163
+ DOM::Document.body << main
164
+ Fron::Sheet.render_style_tag
@@ -0,0 +1,193 @@
1
+ require 'fron_ui'
2
+ require 'fron-ui/utils/theme_roller'
3
+
4
+ class String
5
+ def to_class
6
+ split('::').inject(Object) do |mod, class_name|
7
+ mod.const_get(class_name)
8
+ end
9
+ end
10
+ end
11
+
12
+ class Item < UI::Label
13
+ include Record
14
+
15
+ style padding: -> { theme.spacing.em }
16
+
17
+ def render
18
+ self.text = @data[:id]
19
+ end
20
+ end
21
+
22
+ class List < UI::List
23
+ include UI::Behaviors::SelectableChildren
24
+ end
25
+
26
+ class Demo < UI::Box
27
+ style display: :flex,
28
+ justifyContent: :center,
29
+ alignItems: :center,
30
+ '> *' => {
31
+ maxHeight: '85%',
32
+ maxWidth: '85%'
33
+ }
34
+ end
35
+
36
+ class Field < UI::Container
37
+ extend Forwardable
38
+
39
+ component :label, UI::Label, flex: 1
40
+
41
+ def_delegators :input, :value, :value=
42
+ def_delegators :label, :text, :text=
43
+
44
+ style width: 20.em
45
+ end
46
+
47
+ class CheckboxField < Field
48
+ component :input, UI::Checkbox
49
+
50
+ style 'ui-checkbox' => { order: 1,
51
+ marginLeft: 0 },
52
+ 'ui-label' => { order: 2,
53
+ lineHeight: 2.em,
54
+ marginLeft: -> { theme.spacing.em } }
55
+
56
+ def initialize
57
+ super
58
+ self[:direction] = :row
59
+ self.compact = true
60
+ end
61
+ end
62
+
63
+ class InputField < Field
64
+ component :input, UI::Input
65
+ end
66
+
67
+ class RangeField < Field
68
+ component :input, UI::NumberRange
69
+ end
70
+
71
+ class Main < UI::Container
72
+ tag 'main'
73
+
74
+ style padding: -> { theme.spacing.em },
75
+ boxSizing: 'border-box',
76
+ overflow: :hidden,
77
+ margin: '0 auto',
78
+ height: '100vh',
79
+ '> ui-container > ui-container > ui-box' => {
80
+ flexWrap: :wrap,
81
+ height: 10.3.em
82
+ }
83
+
84
+ component :container, UI::Container, direction: :row, flex: 1 do
85
+ component :sidebar, UI::Box do
86
+ component :title, UI::Title, text: 'Components'
87
+ component :list, List, base: ::Item
88
+ end
89
+
90
+ component :demo_container, UI::Container, flex: 1 do
91
+ component :demo, Demo, flex: 1
92
+ component :options, UI::Box
93
+ end
94
+
95
+ component :theme, ThemeRoller
96
+ end
97
+
98
+ on :selected_change, 'list', :populate
99
+
100
+ def populate(event)
101
+ data = event.target.selected.data
102
+ el = data[:id].to_class.new
103
+ data[:proc].call el if data[:proc]
104
+ data[:args].to_h.each do |key, value|
105
+ if el.respond_to?("#{key}=")
106
+ el.send("#{key}=", value)
107
+ else
108
+ el[key] = value
109
+ end
110
+ end
111
+ oc = @container.demo_container.options
112
+ oc.empty
113
+ data[:options].to_h.each do |key, value|
114
+ input = case value
115
+ when :range
116
+ RangeField.new
117
+ when :input
118
+ InputField.new
119
+ when :checkbox
120
+ CheckboxField.new
121
+ end
122
+ input.text = key
123
+ input.value = el.send key
124
+ input.on :change do
125
+ el.send("#{key}=", input.value)
126
+ end
127
+ oc << input
128
+ end
129
+ request_animation_frame do
130
+ @container.demo_container.demo.empty
131
+ @container.demo_container.demo << el
132
+ end
133
+ end
134
+ end
135
+
136
+ CHOOSER_ITEMS = [
137
+ { id: 'en', value: 'English' },
138
+ { id: 'hu', value: 'Hungarian' },
139
+ { id: 'fr', value: 'French' },
140
+ { id: 'gr', value: 'German' },
141
+ { id: 'aar', value: 'Afar' },
142
+ { id: 'abk', value: 'Abkhazian' },
143
+ { id: 'afr', value: 'Afrikaans' },
144
+ { id: 'aka', value: 'Akan' },
145
+ { id: 'amh', value: 'Amharic' },
146
+ { id: 'ara', value: 'Arabic' },
147
+ { id: 'arg', value: 'Aragonese' },
148
+ { id: 'asm', value: 'Assamese' },
149
+ { id: 'ava', value: 'Avaric' },
150
+ { id: 'ave', value: 'Avestan' },
151
+ { id: 'aym', value: 'Aymara' },
152
+ { id: 'aze', value: 'Azerbaijani' },
153
+ { id: 'bak', value: 'Bashkir' },
154
+ { id: 'bam', value: 'Bambara' },
155
+ { id: 'bel', value: 'Belarusian' },
156
+ { id: 'ben', value: 'Bengali' },
157
+ { id: 'bih', value: 'Bihari' },
158
+ { id: 'bis', value: 'Bislama' }
159
+ ]
160
+
161
+ Fron::Sheet.add_rule 'body', { margin: 0, fontSize: 16.px }, '0'
162
+
163
+ create_tabs = lambda do |tabs|
164
+ (1..3).each do |index|
165
+ el = UI::Tabs::Tab.new
166
+ el[:tab] = "Tab #{index}"
167
+ el.html = Lorem.paragraph
168
+ tabs << el
169
+ end
170
+ end
171
+
172
+ data = [
173
+ { id: 'UI::Button', args: { text: 'Button...' }, options: { text: :input } },
174
+ { id: 'UI::DatePicker' },
175
+ { id: 'UI::ColorPicker', args: { value: '#328B3F' } },
176
+ { id: 'UI::Checkbox', args: { checked: true } },
177
+ { id: 'UI::Calendar' },
178
+ { id: 'UI::ColorPanel' },
179
+ { id: 'UI::Slider' },
180
+ { id: 'UI::Chooser', args: { items: CHOOSER_ITEMS, placeholder: 'Choose language...' }, options: { disabled: :checkbox, multiple: :checkbox, placeholder: :input, searchable: :checkbox, deselectable: :checkbox } },
181
+ { id: 'UI::Loader', args: { loading: true } },
182
+ { id: 'UI::Image', args: { src: 'http://m3.i.pbase.com/o6/90/547190/1/116543443.KT2b8KYm.IMG_9562.jpg', width: 800.px, height: 533.px } },
183
+ { id: 'UI::Progress', args: { value: 0.1 } },
184
+ { id: 'UI::Tabs', args: {}, proc: create_tabs },
185
+ { id: 'UI::NumberRange', args: { affix: 'em', label: 'width:', min: 0, max: 10, step: 0.1 }, options: { affix: :input, label: :input, min: :range, max: :range, step: :range, round: :range } }
186
+ ]
187
+
188
+ main = Main.new
189
+ main.container.sidebar.list.items = data
190
+ main.container.sidebar.list.select main.container.sidebar.list.children.last
191
+
192
+ DOM::Document.body << main
193
+ Fron::Sheet.render_style_tag