aaf-lipstick 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+ module Lipstick
3
+ module Helpers
4
+ class FormValidationBuilder
5
+ def initialize(sym)
6
+ @rules = {}
7
+ @messages = {}
8
+ @sym = sym
9
+ end
10
+
11
+ def to_h
12
+ { rules: @rules, messages: @messages }
13
+ end
14
+
15
+ def validate_field(field, opts)
16
+ target = wrap_name(field)
17
+
18
+ opts.each do |k, v|
19
+ next validate_with_hash(target, k, v) if v.is_a?(Hash)
20
+ validate_with_param(target, k, v)
21
+ end
22
+ end
23
+
24
+ def auto_validate(obj, *fields)
25
+ unless obj.class.respond_to?(:lipstick_auto_validators)
26
+ raise("#{obj.class.name} does not include Lipstick::AutoValidation")
27
+ end
28
+
29
+ validators = obj.class.lipstick_auto_validators
30
+ validators.slice(*fields).each do |field, opts|
31
+ validate_field(field, opts)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def validate_with_hash(target, validation, opts)
38
+ message = opts.delete(:message)
39
+ add_message(target, validation, message) if message
40
+
41
+ return validate_with_param(target, validation, true) if opts.empty?
42
+
43
+ if opts.keys == [:param]
44
+ return validate_with_param(target, validation, opts[:param])
45
+ end
46
+
47
+ add_rule(target, validation, opts)
48
+ end
49
+
50
+ def validate_with_param(target, validation, param)
51
+ add_rule(target, validation, param)
52
+ end
53
+
54
+ def add_rule(target, key, value)
55
+ @rules[target] ||= {}
56
+ @rules[target][key] = value
57
+ end
58
+
59
+ def add_message(target, key, value)
60
+ @messages[target] ||= {}
61
+ @messages[target][key] = value
62
+ end
63
+
64
+ def wrap_name(name)
65
+ return name if @sym.nil?
66
+ "#{@sym}[#{name}]"
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,23 +1,20 @@
1
- require 'action_view'
2
-
1
+ # frozen_string_literal: true
3
2
  module Lipstick
4
3
  module Helpers
5
4
  module LayoutHelper
6
- include ActionView::Helpers::TagHelper
7
- include ActionView::Helpers::TextHelper
8
- include ActionView::Helpers::CaptureHelper
9
-
10
- def aaf_header(title:, environment: nil, &bl)
5
+ def aaf_header(title:, environment: nil, auth: nil, &bl)
11
6
  content_tag('div', class: 'aaf-header') do
12
- concat(aaf_banner(title, environment))
7
+ concat(aaf_banner(title, environment, auth))
13
8
  concat(capture(&bl))
14
9
  end
15
10
  end
16
11
 
17
- def aaf_footer(&bl)
12
+ def aaf_footer
18
13
  content_tag('footer') do
19
- concat(content_tag('div', '', class: 'ui divider'))
20
- concat(capture(&bl))
14
+ content_tag('div', class: 'footer-content') do
15
+ concat(aaf_logo)
16
+ concat(capture { yield })
17
+ end
21
18
  end
22
19
  end
23
20
 
@@ -31,25 +28,23 @@ module Lipstick
31
28
  end
32
29
 
33
30
  def page_header(header, subheader = nil)
34
- content_tag('h2', class: 'ui header') do
35
- concat(header)
36
- if subheader
37
- concat(content_tag('div', subheader, class: 'sub header'))
31
+ content_tag('div', class: 'page-header') do
32
+ content_tag('h1') do
33
+ concat(header)
34
+ concat(' ')
35
+ concat(content_tag('small', subheader)) if subheader
38
36
  end
39
37
  end
40
38
  end
41
39
 
42
- def divider_tag
43
- content_tag('div', '', class: 'ui divider')
44
- end
45
-
46
40
  def yes_no_string(boolean)
47
41
  boolean ? 'Yes' : 'No'
48
42
  end
49
43
 
50
44
  def icon_tag(icon_class, html_opts = {})
51
- html_opts[:class] = "#{html_opts[:class]} icon #{icon_class}".strip
52
- content_tag('i', '', html_opts)
45
+ html_opts[:class] =
46
+ "#{html_opts[:class]} glyphicon glyphicon-#{icon_class}".strip
47
+ content_tag('span', '', html_opts)
53
48
  end
54
49
 
55
50
  # button_link_to(url_opts) { 'Link Text' }
@@ -60,77 +55,123 @@ module Lipstick
60
55
  args.unshift(capture(&block)) if block_given?
61
56
  text, url_opts, html_opts = args
62
57
  html_opts ||= {}
63
- html_opts[:class] = "#{html_opts[:class]} ui button".strip
58
+ html_opts[:class] ||= 'btn-default'
59
+ html_opts[:class] = "#{html_opts[:class]} btn".strip
64
60
  link_to(text, url_opts, html_opts)
65
61
  end
66
62
 
67
63
  def info_message(title, &block)
68
- message_block(title, 'info', 'info', &block)
64
+ alert_block(title, 'info', &block)
69
65
  end
70
66
 
71
67
  def error_message(title, &block)
72
- message_block(title, 'error', 'warning', &block)
68
+ alert_block(title, 'danger', &block)
73
69
  end
74
70
 
75
71
  def success_message(title, &block)
76
- message_block(title, 'success', 'smile', &block)
72
+ alert_block(title, 'success', &block)
77
73
  end
78
74
 
79
75
  def warning_message(title, &block)
80
- message_block(title, 'warning', 'warning', &block)
76
+ alert_block(title, 'warning', &block)
81
77
  end
82
78
 
83
79
  def breadcrumbs(*links)
84
- content_tag('div', class: 'ui breadcrumb') do
80
+ content_tag('ol', class: 'breadcrumb') do
85
81
  last = links.pop
86
82
 
87
83
  links.each do |link|
88
- concat(content_tag('div', breadcrumb_link(link), class: 'section'))
89
- concat(icon_tag('angle double right divider'))
84
+ concat(content_tag('li', breadcrumb_link(link)))
90
85
  end
91
86
 
92
- concat(content_tag('div', breadcrumb_link(last),
93
- class: 'active section'))
87
+ concat(content_tag('li', breadcrumb_link(last),
88
+ class: 'active'))
94
89
  end
95
90
  end
96
91
 
92
+ def will_paginate(_ = nil, options = {})
93
+ options[:renderer] ||= Lipstick::Helpers::PaginationLinkRenderer
94
+ super
95
+ end
96
+
97
+ DISABLE_ANIMATIONS_CSS = <<-EOF
98
+ .ui, .ui * {
99
+ -webkit-animation-duration: 0ms !important;
100
+ -moz-animation-duration: 0ms !important;
101
+ -ms-animation-duration: 0ms !important;
102
+ animation-duration: 0ms !important;
103
+ }
104
+ EOF
105
+ private_constant :DISABLE_ANIMATIONS_CSS
106
+
107
+ def disable_animations
108
+ content_tag('style', DISABLE_ANIMATIONS_CSS, type: 'text/css')
109
+ end
110
+
97
111
  private
98
112
 
99
- def aaf_banner(title, environment)
100
- content_tag('header', class: 'banner clearfix') do
101
- concat(aaf_environment_string(environment))
102
- header = content_tag('h2', class: 'ui inverted header') do
103
- # The logo width is also forced in CSS. The logo is scaled to 50% in
104
- # both width and height to improve quality on high-density displays.
105
- concat(tag('img', src: image_path('logo.png'), class: 'logo',
106
- width: 138, height: 80))
107
- concat(content_tag('div', title, class: 'content'))
113
+ def aaf_banner(title, environment, auth)
114
+ capture do
115
+ text = content_tag('header') do
116
+ concat(aaf_links(auth))
117
+ concat(title)
118
+ concat(aaf_environment_string(environment))
108
119
  end
109
- concat(header)
120
+ concat(text)
110
121
  end
111
122
  end
112
123
 
113
- def aaf_environment_string(environment)
114
- return if environment.nil?
115
- content_tag('div', environment, class: 'environment')
124
+ def aaf_logo
125
+ content_tag('span', '', class: 'pull-right logo hidden-xs hidden-sm')
116
126
  end
117
127
 
118
- def image_path(*)
119
- unless defined?(super)
120
- fail('No image_path method was found. This is typically provided by' \
121
- ' Rails or Sprockets::Helpers')
128
+ def aaf_links(auth)
129
+ content_tag('div', class: 'aaf-links pull-right') do
130
+ concat(aaf_home_link)
131
+ concat(aaf_support_link)
132
+ concat(aaf_auth_link(auth))
122
133
  end
123
- super
124
134
  end
125
135
 
126
- def message_block(title, color_class, icon_class, &block)
127
- content_tag('div', class: "ui icon message #{color_class}") do
128
- concat(icon_tag(icon_class))
129
- inner = content_tag('div', class: 'content') do
130
- concat(content_tag('div', title, class: 'header'))
131
- concat(capture(&block))
132
- end
133
- concat(inner)
136
+ def aaf_home_link
137
+ content_tag('a', href: 'https://aaf.edu.au') do
138
+ concat(icon_tag('aaf'))
139
+ concat(' AAF Home')
140
+ end
141
+ end
142
+
143
+ def aaf_support_link
144
+ content_tag('a', href: 'https://support.aaf.edu.au') do
145
+ concat(icon_tag('user'))
146
+ concat(' Support')
147
+ end
148
+ end
149
+
150
+ def aaf_auth_link(auth)
151
+ return if auth.nil?
152
+
153
+ text = ' Log In'
154
+ text = ' Log Out' if auth == :logout
155
+
156
+ icon = 'log-in'
157
+ icon = 'log-out' if auth == :logout
158
+
159
+ content_tag('a', href: "/auth/#{auth}") do
160
+ concat(icon_tag(icon))
161
+ concat(text)
162
+ end
163
+ end
164
+
165
+ def aaf_environment_string(environment)
166
+ return unless environment&.present?
167
+ content_tag('span', environment, class: 'environment')
168
+ end
169
+
170
+ def alert_block(title, color_class, &block)
171
+ opts = { class: "alert alert-#{color_class}", role: 'alert' }
172
+ content_tag('div', opts) do
173
+ concat(content_tag('h4', title))
174
+ concat(capture(&block))
134
175
  end
135
176
  end
136
177
 
@@ -1,48 +1,43 @@
1
+ # frozen_string_literal: true
1
2
  module Lipstick
2
3
  module Helpers
3
4
  module NavHelper
4
- include ActionView::Helpers::TagHelper
5
- include ActionView::Helpers::TextHelper
5
+ def nav_bar
6
+ content_tag('nav', class: 'navbar shrink') { yield }
7
+ end
6
8
 
7
- def nav_bar(&block)
8
- content_tag('nav') do
9
- css_class = 'ui borderless inverted menu collapsing nav parent'
10
- content_tag('div', class: css_class) do
11
- concat(nav_collapse_button)
12
- concat(capture(&block))
13
- end
9
+ def nav_first_item(text, url)
10
+ content_tag('div', class: 'nav navbar-header') do
11
+ concat(nav_collapse_button)
12
+ concat(content_tag('a', text, href: url, class: 'navbar-brand'))
14
13
  end
15
14
  end
16
15
 
17
- def nav_collapsing_items(&block)
18
- content_tag('span', class: 'collapsing nav content', &block)
16
+ def nav_collapsing_items
17
+ attrs = { class: 'collapse navbar-collapse', id: 'aaf-nav-collapse' }
18
+ content_tag('div', attrs) do
19
+ content_tag('ul', class: 'nav navbar-nav') { yield }
20
+ end
19
21
  end
20
22
 
21
23
  def nav_item(text, url, html_opts = {})
22
- html_opts[:class] = "#{html_opts[:class]} item".strip
23
- content_tag('a', text, html_opts.merge(href: url))
24
- end
25
-
26
- def nav_items_right(&block)
27
- content_tag('div', class: 'right menu', &block)
28
- end
29
-
30
- def nav_dropdown(title, &block)
31
- content_tag('div', class: 'ui simple dropdown item') do
32
- concat(content_tag('i', '', class: 'dropdown icon'))
33
- concat(title)
34
- concat(content_tag('div', class: 'menu', &block))
24
+ content_tag('li') do
25
+ content_tag('a', text, html_opts.merge(href: url))
35
26
  end
36
27
  end
37
28
 
38
29
  private
39
30
 
40
31
  def nav_collapse_button
41
- content_tag('div', class: 'collapsing nav button') do
42
- content_tag('a', href: '#',
43
- class: 'ui icon mini black open button') do
44
- content_tag('i', '', class: 'list layout icon')
45
- end
32
+ attrs = {
33
+ type: 'button', class: 'navbar-toggle collapsed',
34
+ 'data-toggle': 'collapse', 'data-target': '#aaf-nav-collapse',
35
+ 'aria-expanded': 'false'
36
+ }
37
+
38
+ content_tag('button', attrs) do
39
+ concat(content_tag('span', 'Toggle navigation', class: 'sr-only'))
40
+ 3.times { concat(content_tag('span', '', class: 'icon-bar')) }
46
41
  end
47
42
  end
48
43
  end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+ require 'will_paginate'
3
+
4
+ module Lipstick
5
+ module Helpers
6
+ class PaginationLinkRenderer
7
+ attr_reader :items, :current, :total, :template
8
+ private :current, :total, :template
9
+
10
+ def prepare(collection, _options, template)
11
+ @current = collection.current_page
12
+ @total = collection.total_pages
13
+
14
+ @items = [*prefix, *middle, *suffix]
15
+ @template = template
16
+ end
17
+
18
+ def to_html
19
+ template.content_tag('nav', class: 'pagination-wrapper') do
20
+ template.content_tag('ul', class: 'pagination') do
21
+ items.each do |item|
22
+ template.concat(render_item(item))
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def prefix
31
+ return [:prev] if !show_first? || short_list?
32
+ return [:prev, 1, :gap] if prefix_gap?
33
+ [:prev, 1]
34
+ end
35
+
36
+ def middle
37
+ return page_range(1, total) if short_list?
38
+ page_range(*page_window)
39
+ end
40
+
41
+ # Decides which pages to show in the links surrounding the "current" page.
42
+ def page_window
43
+ # If we're close to the start/end we can show a few extra page links.
44
+ return [1, 8] if current < 5
45
+ return [total - 7, total] if current > total - 4
46
+
47
+ # Otherwise show 3 pages either side of the current page
48
+ [current - 3, current + 3]
49
+ end
50
+
51
+ def suffix
52
+ return [:next] if !show_last? || short_list?
53
+ return [:gap, total, :next] if suffix_gap?
54
+ [total, :next]
55
+ end
56
+
57
+ def page_range(start, finish)
58
+ [start, 1].max.upto([finish, total].min)
59
+ end
60
+
61
+ def short_list?
62
+ total <= 10
63
+ end
64
+
65
+ def show_first?
66
+ !middle.include?(1)
67
+ end
68
+
69
+ def show_last?
70
+ !middle.include?(total)
71
+ end
72
+
73
+ def prefix_gap?
74
+ !middle.include?(2)
75
+ end
76
+
77
+ def suffix_gap?
78
+ !middle.include?(total - 1)
79
+ end
80
+
81
+ def render_item(item)
82
+ case item
83
+ when :prev
84
+ prev_item
85
+ when :next
86
+ next_item
87
+ when :gap
88
+ gap_item
89
+ else
90
+ link_to_page(item, page: item)
91
+ end
92
+ end
93
+
94
+ def prev_item
95
+ icon = template.content_tag('span', "\u25C0", 'aria-hidden': 'true')
96
+
97
+ return disabled_item(icon) if current < 2
98
+ link_to_page(icon, page: current - 1, 'aria-label': 'Previous')
99
+ end
100
+
101
+ def next_item
102
+ icon = template.content_tag('span', "\u25B6", 'aria-hidden': 'true')
103
+
104
+ return disabled_item(icon) if current >= total
105
+ link_to_page(icon, page: current + 1, 'aria-label': 'Next')
106
+ end
107
+
108
+ def gap_item
109
+ template.content_tag('li') do
110
+ template.content_tag('span', "\u2026", 'aria-hidden': 'true')
111
+ end
112
+ end
113
+
114
+ def link_to_page(content, page:, **opts)
115
+ li_opts = {}
116
+ li_opts[:class] = 'active' if page == current
117
+
118
+ url_opts = template.params.merge(page: page)
119
+ opts = opts.merge(href: template.url_for(url_opts))
120
+
121
+ template.content_tag('li', li_opts) do
122
+ template.content_tag('a', content, opts)
123
+ end
124
+ end
125
+
126
+ def disabled_item(content)
127
+ template.content_tag('li', content, class: 'disabled')
128
+ end
129
+ end
130
+ end
131
+ end