docks_theme_api 1.0.2

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.
Files changed (237) hide show
  1. checksums.yaml +15 -0
  2. data/.babelrc +4 -0
  3. data/.editorconfig +8 -0
  4. data/.eslintrc +115 -0
  5. data/.gitignore +24 -0
  6. data/.rubocop.yml +20 -0
  7. data/.travis.yml +16 -0
  8. data/Gemfile +4 -0
  9. data/README.md +5 -0
  10. data/Rakefile +3 -0
  11. data/assets/images/icons.svg +63 -0
  12. data/assets/scripts/coffeescript/pattern_library_helpers.coffee +8 -0
  13. data/assets/scripts/javascript/pattern_library_helpers.js +11 -0
  14. data/assets/scripts/pattern_library.js +10380 -0
  15. data/assets/scripts/pattern_library_demo.js +0 -0
  16. data/assets/styles/less/pattern-library-helpers.less +103 -0
  17. data/assets/styles/pattern-library-demo.css +1882 -0
  18. data/assets/styles/pattern-library.css +1882 -0
  19. data/assets/styles/sass/pattern-library-helpers.sass +90 -0
  20. data/assets/styles/scss/pattern-library-helpers.scss +99 -0
  21. data/assets/styles/stylus/pattern-library-helpers.styl +90 -0
  22. data/assets/templates/erb/demo.erb +26 -0
  23. data/assets/templates/erb/layouts/demo.erb +17 -0
  24. data/assets/templates/erb/layouts/pattern.erb +76 -0
  25. data/assets/templates/erb/partials/sidebar.erb +124 -0
  26. data/assets/templates/erb/partials/symbols/class.erb +1 -0
  27. data/assets/templates/erb/partials/symbols/demo.erb +40 -0
  28. data/assets/templates/erb/partials/symbols/factory.erb +70 -0
  29. data/assets/templates/erb/partials/symbols/function.erb +103 -0
  30. data/assets/templates/erb/partials/symbols/mixin.erb +62 -0
  31. data/assets/templates/erb/partials/symbols/variable.erb +59 -0
  32. data/assets/templates/erb/pattern.erb +102 -0
  33. data/assets/templates/haml/demo.haml +14 -0
  34. data/assets/templates/haml/layouts/demo.haml +6 -0
  35. data/assets/templates/haml/layouts/pattern.haml +38 -0
  36. data/assets/templates/haml/partials/sidebar.haml +68 -0
  37. data/assets/templates/haml/partials/symbols/class.haml +1 -0
  38. data/assets/templates/haml/partials/symbols/demo.haml +23 -0
  39. data/assets/templates/haml/partials/symbols/factory.haml +38 -0
  40. data/assets/templates/haml/partials/symbols/function.haml +54 -0
  41. data/assets/templates/haml/partials/symbols/mixin.haml +31 -0
  42. data/assets/templates/haml/partials/symbols/variable.haml +22 -0
  43. data/assets/templates/haml/pattern.haml +54 -0
  44. data/assets/templates/slim/demo.slim +24 -0
  45. data/assets/templates/slim/layouts/demo.slim +5 -0
  46. data/assets/templates/slim/layouts/pattern.slim +48 -0
  47. data/assets/templates/slim/partials/sidebar.slim +112 -0
  48. data/assets/templates/slim/partials/symbols/class.slim +1 -0
  49. data/assets/templates/slim/partials/symbols/demo.slim +30 -0
  50. data/assets/templates/slim/partials/symbols/factory.slim +57 -0
  51. data/assets/templates/slim/partials/symbols/function.slim +81 -0
  52. data/assets/templates/slim/partials/symbols/mixin.slim +45 -0
  53. data/assets/templates/slim/partials/symbols/variable.slim +35 -0
  54. data/assets/templates/slim/pattern.slim +63 -0
  55. data/docks_config.rb +32 -0
  56. data/docks_theme_api.gemspec +37 -0
  57. data/gulpfile.js +88 -0
  58. data/karma.conf.js +6 -0
  59. data/lib/docks_theme_api/components/base_component.rb +99 -0
  60. data/lib/docks_theme_api/components/code_block_component.rb +10 -0
  61. data/lib/docks_theme_api/components/popover_component.rb +15 -0
  62. data/lib/docks_theme_api/components/table_component.rb +34 -0
  63. data/lib/docks_theme_api/components/tablist_component.rb +11 -0
  64. data/lib/docks_theme_api/components.rb +21 -0
  65. data/lib/docks_theme_api/helpers/ui_helper.rb +69 -0
  66. data/lib/docks_theme_api/theme.rb +21 -0
  67. data/lib/docks_theme_api.rb +1 -0
  68. data/package.json +60 -0
  69. data/source/behaviors/filterable/filterable.coffee +353 -0
  70. data/source/behaviors/filterable/filterable.js +0 -0
  71. data/source/behaviors/filterable/filterable.scss +34 -0
  72. data/source/behaviors/filterable/package.json +3 -0
  73. data/source/behaviors/index.js +0 -0
  74. data/source/components/avatar/avatar.erb +20 -0
  75. data/source/components/avatar/avatar.js +142 -0
  76. data/source/components/avatar/avatar.scss +200 -0
  77. data/source/components/avatar/avatar_container.erb +13 -0
  78. data/source/components/avatar/package.json +3 -0
  79. data/source/components/avatar/spec/avatar_spec.js +81 -0
  80. data/source/components/badge/badge.scss +158 -0
  81. data/source/components/button/button.scss +213 -0
  82. data/source/components/card/card.scss +32 -0
  83. data/source/components/code_block/code-block.scss +353 -0
  84. data/source/components/code_block/code_block.erb +95 -0
  85. data/source/components/code_block/code_block.js +444 -0
  86. data/source/components/code_block/package.json +3 -0
  87. data/source/components/code_block/spec/code_block_spec.js +10 -0
  88. data/source/components/demo/demo.js +244 -0
  89. data/source/components/demo/demo.scss +90 -0
  90. data/source/components/demo/package.json +3 -0
  91. data/source/components/exploded/exploded.erb +25 -0
  92. data/source/components/exploded/exploded.js +694 -0
  93. data/source/components/exploded/exploded.scss +166 -0
  94. data/source/components/exploded/package.json +3 -0
  95. data/source/components/field/field.js +24 -0
  96. data/source/components/field/field.scss +101 -0
  97. data/source/components/field/package.json +3 -0
  98. data/source/components/header/header.scss +33 -0
  99. data/source/components/iframe/iframe.erb +12 -0
  100. data/source/components/iframe/iframe.js +381 -0
  101. data/source/components/iframe/package.json +3 -0
  102. data/source/components/index.js +37 -0
  103. data/source/components/inline_group/inline-group.scss +14 -0
  104. data/source/components/internal_link/internal_link.js +49 -0
  105. data/source/components/internal_link/package.json +3 -0
  106. data/source/components/list/list.scss +230 -0
  107. data/source/components/modal/modal.coffee +84 -0
  108. data/source/components/modal/modal.erb +19 -0
  109. data/source/components/modal/modal.js +0 -0
  110. data/source/components/modal/modal.scss +57 -0
  111. data/source/components/modal/package.json +3 -0
  112. data/source/components/notice/notice.scss +48 -0
  113. data/source/components/popover/package.json +3 -0
  114. data/source/components/popover/popover.coffee +562 -0
  115. data/source/components/popover/popover.erb +21 -0
  116. data/source/components/popover/popover.js +0 -0
  117. data/source/components/popover/popover.scss +139 -0
  118. data/source/components/range/range.scss +78 -0
  119. data/source/components/resizable/package.json +3 -0
  120. data/source/components/resizable/resizable.erb +30 -0
  121. data/source/components/resizable/resizable.js +250 -0
  122. data/source/components/resizable/resizable.scss +245 -0
  123. data/source/components/resizable/size_buttons.js +249 -0
  124. data/source/components/scroll_container/package.json +3 -0
  125. data/source/components/scroll_container/scroll-container.scss +4 -0
  126. data/source/components/scroll_container/scroll_container.js +24 -0
  127. data/source/components/section/section.scss +99 -0
  128. data/source/components/select/package.json +3 -0
  129. data/source/components/select/select.erb +21 -0
  130. data/source/components/select/select.js +35 -0
  131. data/source/components/select/select.scss +163 -0
  132. data/source/components/table/package.json +3 -0
  133. data/source/components/table/table.erb +16 -0
  134. data/source/components/table/table.js +351 -0
  135. data/source/components/table/table.scss +236 -0
  136. data/source/components/tablist/package.json +3 -0
  137. data/source/components/tablist/tablist.erb +13 -0
  138. data/source/components/tablist/tablist.js +246 -0
  139. data/source/components/tablist/tablist.scss +191 -0
  140. data/source/components/tablist/tablist_panel.erb +14 -0
  141. data/source/components/tablist/tablist_tab.erb +20 -0
  142. data/source/components/toggle/package.json +3 -0
  143. data/source/components/toggle/toggle.erb +11 -0
  144. data/source/components/toggle/toggle.js +211 -0
  145. data/source/components/toggle/toggle_container.erb +30 -0
  146. data/source/components/vertical_spacer/vertical-spacer.scss +3 -0
  147. data/source/components/vertical_stack/vertical-stack.scss +19 -0
  148. data/source/components/xray/package.json +3 -0
  149. data/source/components/xray/xray.erb +50 -0
  150. data/source/components/xray/xray.js +123 -0
  151. data/source/components/xray/xray.scss +79 -0
  152. data/source/foundation/app/app.js +15 -0
  153. data/source/foundation/app/package.json +3 -0
  154. data/source/pattern-library-demo.scss +13 -0
  155. data/source/pattern-library.scss +13 -0
  156. data/source/pattern_library.js +8 -0
  157. data/source/pattern_library_demo.js +8 -0
  158. data/source/structures/index.js +11 -0
  159. data/source/structures/sidebar/package.json +3 -0
  160. data/source/structures/sidebar/sidebar.js +69 -0
  161. data/source/structures/sidebar/sidebar.scss +79 -0
  162. data/source/utilities/builder/builder.js +138 -0
  163. data/source/utilities/builder/package.json +3 -0
  164. data/source/utilities/client/client.js +7 -0
  165. data/source/utilities/client/package.json +3 -0
  166. data/source/utilities/colors/colors.scss +112 -0
  167. data/source/utilities/defaults/defaults.scss +38 -0
  168. data/source/utilities/dom_cache/dom_cache.js +24 -0
  169. data/source/utilities/dom_cache/package.json +3 -0
  170. data/source/utilities/events/events.js +25 -0
  171. data/source/utilities/events/package.json +3 -0
  172. data/source/utilities/font_sizes/font-sizes.scss +85 -0
  173. data/source/utilities/foundation/a11y.scss +10 -0
  174. data/source/utilities/foundation/base.scss +29 -0
  175. data/source/utilities/foundation/icon.scss +114 -0
  176. data/source/utilities/foundation/layout.scss +67 -0
  177. data/source/utilities/foundation/page.scss +39 -0
  178. data/source/utilities/foundation/type.scss +208 -0
  179. data/source/utilities/functions/functions.scss +127 -0
  180. data/source/utilities/keycodes/keycodes.js +23 -0
  181. data/source/utilities/keycodes/package.json +3 -0
  182. data/source/utilities/markup/markup.js +90 -0
  183. data/source/utilities/markup/package.json +3 -0
  184. data/source/utilities/media/media.scss +172 -0
  185. data/source/utilities/mixins/mixins.scss +89 -0
  186. data/source/utilities/naming_convention/naming_convention.js +3 -0
  187. data/source/utilities/naming_convention/package.json +3 -0
  188. data/source/utilities/numbers/numbers.js +14 -0
  189. data/source/utilities/numbers/package.json +3 -0
  190. data/source/utilities/painting/package.json +3 -0
  191. data/source/utilities/painting/painting.js +7 -0
  192. data/source/utilities/pattern/package.json +3 -0
  193. data/source/utilities/pattern/pattern.js +50 -0
  194. data/source/utilities/query_string/package.json +3 -0
  195. data/source/utilities/query_string/query_string.js +24 -0
  196. data/source/utilities/template/package.json +3 -0
  197. data/source/utilities/template/template.js +10 -0
  198. data/source/utilities/text_range/package.json +3 -0
  199. data/source/utilities/text_range/text_range.js +30 -0
  200. data/source/utilities/ui_events/package.json +3 -0
  201. data/source/utilities/ui_events/ui_events.js +85 -0
  202. data/source/utilities/variables/variables.scss +18 -0
  203. data/source/utilities/z_indexes/z-indexes.scss +88 -0
  204. data/source/vendor/array_includes.js +28 -0
  205. data/source/vendor/highlight.js +1142 -0
  206. data/source/vendor/index.js +1 -0
  207. data/source/vendor/matrix.js +399 -0
  208. data/source/vendor/query_string.js +66 -0
  209. data/spec/assets/.eslintrc +9 -0
  210. data/spec/assets/spec_fixture.js +33 -0
  211. data/spec/assets/spec_helper.js +19 -0
  212. data/spec/lib/components/base_component_spec.rb +156 -0
  213. data/spec/lib/components_spec.rb +30 -0
  214. data/spec/lib/helpers/ui_helper_spec.rb +62 -0
  215. data/spec/lib/theme_spec.rb +25 -0
  216. data/spec/spec_helper.rb +15 -0
  217. data/tasks/gulp/.eslintrc +6 -0
  218. data/tasks/gulp/browser_sync.js +8 -0
  219. data/tasks/gulp/code_quality/scripts.js +10 -0
  220. data/tasks/gulp/config/index.js +116 -0
  221. data/tasks/gulp/minify/scripts.js +13 -0
  222. data/tasks/gulp/minify/styles.js +13 -0
  223. data/tasks/gulp/pattern_library/index.js +5 -0
  224. data/tasks/gulp/pattern_library/scripts.js +10 -0
  225. data/tasks/gulp/pattern_library/styles.js +10 -0
  226. data/tasks/gulp/scripts.js +8 -0
  227. data/tasks/gulp/spec/scripts.js +11 -0
  228. data/tasks/gulp/styles.js +17 -0
  229. data/tasks/gulp/utilities/babel/relative_require.js +22 -0
  230. data/tasks/gulp/utilities/babel/spec_helper.js +20 -0
  231. data/tasks/gulp/utilities/browserify_bundler.js +22 -0
  232. data/tasks/gulp/utilities/handle_errors.js +13 -0
  233. data/tasks/gulp/watch.js +9 -0
  234. data/tasks/rake/rspec.rake +7 -0
  235. data/tasks/rake/rubocop.rake +8 -0
  236. data/tasks/rake/templates.rake +50 -0
  237. metadata +470 -0
@@ -0,0 +1,353 @@
1
+ #*
2
+ # @page Filterable
3
+ # @group Behavior
4
+ #
5
+ # This behavior provides a set of attributes that allow you to establish an
6
+ # `input`, which acts as the [`Filter`](@link), and any number of connected
7
+ # [`Filterable`](@link) components. The filter, for its part, specifies
8
+ # what notes it filters (through the `data-filters` attribute), on what
9
+ # attribute of the items contained in the filterable it should compare against
10
+ # (through the `data-filter-attribute` attribute). `checkbox` inputs may also
11
+ # provide some additional attributes to specify the meaning of checked/
12
+ # unchecked.
13
+ #
14
+ # The `filterable` specifies which of its items should be filtered using its
15
+ # `data-filter-items` attribute. Filterable containers can have any number of
16
+ # nested groups, each of which will be independently hidden when all of its
17
+ # contained items are filtered out.
18
+ #
19
+ # This behavior is most commonly used in conjunction with the [`List`](@link list::list)
20
+ # component, but is generic enough to work with any container that has children.
21
+
22
+ CLASSES =
23
+ HIDDEN: "filter--is-hidden"
24
+ HIGHLIGHT: "filter--highlight"
25
+
26
+ ATTRS =
27
+ FILTERS: "data-filters"
28
+ ATTRIBUTE: "data-filter-attribute"
29
+ ITEMS: "data-filter-items"
30
+ STATUS: "data-filtered-status"
31
+ CHECKED_VALUE: "data-checked-value"
32
+ UNCHECKED_VALUE: "data-unchecked-value"
33
+
34
+ STATUS =
35
+ HIDDEN: "hidden"
36
+ VISIBLE: "visible"
37
+
38
+ #*
39
+ # Wraps the matching characters in `text` with a span that adds the highlighting
40
+ # class.
41
+ #
42
+ # @private
43
+ # @param {String} text - The text to match within.
44
+ # @param {String} match_text - The sequence of strings that show be highlighted
45
+ # (characters do not to be contiguous in `text`).
46
+ #
47
+ # @returns String - The string with highlights applied.
48
+ #
49
+ # @example
50
+ # highlight_characters("foo bar", "o")
51
+ # # => "f<span class='filter--highlight'>o</span>o bar"
52
+ #
53
+ # @example
54
+ # highlight_characters("foo bar", "obr")
55
+ # # => "f<span class='filter--highlight'>o</span>o <span class='filter--highlight'>b</span>a<span class='filter--highlight'>r</span>"
56
+
57
+ highlight_characters = (text, match_text) ->
58
+ return text unless match_text.length
59
+
60
+ [start_tag, end_tag] = ["<span class='#{CLASSES.HIGHLIGHT}'>", "</span>"]
61
+ [match_text_index, match_text_char] = [0, match_text[0]]
62
+ last_index = 0
63
+ new_text = ""
64
+ old_text = text.trim()
65
+
66
+ for char, index in old_text
67
+ if char.toLowerCase() == match_text_char.toLowerCase()
68
+ new_text += "#{old_text[last_index...index]}#{start_tag}#{char}#{end_tag}"
69
+ last_index = index + 1
70
+ match_text_index += 1
71
+ match_text_char = match_text[match_text_index]
72
+
73
+ break unless match_text_char?
74
+
75
+ new_text += old_text[last_index..-1]
76
+ new_text
77
+
78
+
79
+ #*
80
+ # An individual item that can, conditional on all of the filters that have been
81
+ # applied against it, be either hidden (at least one filter does not match) or
82
+ # visible (all filters currently match).
83
+ #
84
+ # [`Filter`s](@link Filter) can be applied with the
85
+ # [`filter_against`](@link FilterableItem#filter_against) method. The last
86
+ # matching state of all applied filters are recorded, so multiple filters can
87
+ # be applied to an item and the item will respect all of them.
88
+ #
89
+ # @factory
90
+ #
91
+ # @param {HTMLElement} node - The node that will be visually hidden/ visible
92
+ # based on the matching status of all applied filters.
93
+
94
+ FilterableItem = (node) ->
95
+ filters = {}
96
+
97
+ Object.create {},
98
+ #*
99
+ # The DOM element that this `FilterableItem` represents.
100
+ #
101
+ # @type HTMLElement
102
+ # @property
103
+ node: value: node
104
+
105
+ #*
106
+ # Indicates whether or not the DOM element does not match any filters it has
107
+ # been compared against (in which case, the item should be hidden).
108
+ #
109
+ # @type Boolean
110
+ # @property
111
+ is_hidden: get: -> !@is_visible
112
+
113
+ #*
114
+ # Indicates whether or not the DOM element matches all filters it has
115
+ # been compared against (in which case, the item should be visible).
116
+ #
117
+ # @type Boolean
118
+ # @property
119
+ is_visible: get: ->
120
+ for id, filter_matches of filters
121
+ return false unless filter_matches
122
+
123
+ true
124
+
125
+ #*
126
+ # Checks this item against the passed filter. After the matching status of
127
+ # the passed filter is updated, it checks all matching states and updates
128
+ # the `data-filter-status` attribute appropriately.
129
+ #
130
+ # @method
131
+ #
132
+ # @param {Filter} filter - The filter to apply to this item.
133
+ # @returns Boolean - The matching state of the passed filter.
134
+ filter_against: value: (filter) ->
135
+ matches = filter.test(this)
136
+ filters[filter.id] = matches
137
+ node.setAttribute(ATTRS.STATUS, if @is_visible then STATUS.VISIBLE else STATUS.HIDDEN)
138
+ matches
139
+
140
+ #*
141
+ # Removes the effects of the passed filter and updates the filtered status
142
+ # of the item accordingly.
143
+ #
144
+ # @method
145
+ #
146
+ # @param {Filter} filter - The filter to remove.
147
+ remove_filter: value: (filter) -> filters.delete(filter.id)
148
+
149
+ #*
150
+ # A wrapper around a set of filterable items. The root of this object can have
151
+ # a `data-filter-items` attribute, which specifies which of its descendants
152
+ # should be filtered against attached [`Filter`s](@link Filter). If no such
153
+ # attribute is provided, the container's direct children will be used as the
154
+ # items.
155
+ #
156
+ # When a custom selector is provided via `data-filter-items`, this object will
157
+ # go through all of its subcomponents, collecting (on each level of the DOM)
158
+ # items that need to be filtered (those matching the selector) and "nested"
159
+ # `Filterable`s. The nested components will independently be hideable, and
160
+ # `Filterable`s higher up the chain will check whether their contained
161
+ # groups are fully hidden before fully hiding themselves.
162
+ #
163
+ # @factory
164
+ #
165
+ # @param {HTMLElement} root - The node wrapping all filterable items.
166
+ # @param {Object} options - The options for initializing this component.
167
+ #
168
+ # @param {String} options.selector - The selector for filterable items. This is
169
+ # used for nested groups inside a root `Filterable` so that they can properly
170
+ # specify their contained filterable items.
171
+ #
172
+ # @param {Boolean} options.nested - Indicates whether the `Filterable` is
173
+ # nested inside a root `Filterable`. This must be specified in order to prevent
174
+ # cacheing the `Filterable` on its node.
175
+
176
+
177
+ Filterable = do ->
178
+ get_items_and_groups = (root, selector) ->
179
+ children = Array::slice.call(root.children)
180
+
181
+ if selector
182
+ items = []
183
+ groups = []
184
+
185
+ for child in children
186
+ if $(child).is(selector)
187
+ items.push(FilterableItem(child))
188
+ else
189
+ group = Filterable(child, selector: selector, nested: true)
190
+ groups.push(group) if group.is_valid
191
+ else
192
+ items = (FilterableItem(child) for child in children)
193
+ groups = []
194
+
195
+ [items, groups]
196
+
197
+ (root, options = {}) ->
198
+ selector = options.selector || root.getAttribute(ATTRS.ITEMS)
199
+ [items, groups] = get_items_and_groups(root, selector)
200
+
201
+ api = Object.create {},
202
+ #*
203
+ # The node that represents this component in the DOM.
204
+ #
205
+ # @property
206
+ # @type HTMLElement
207
+ root: get: -> root
208
+
209
+ #*
210
+ # Specifies whether or not this `Filterable`, or any nested `Filterable`s,
211
+ # have filterable items. It is used to prevent storing references to nodes
212
+ # that are neither filterable items, nor nested containers of filterable
213
+ # items (for example, headings in lists).
214
+ #
215
+ # @property
216
+ # @type Boolean
217
+ is_valid: get: -> items.length > 0 || groups.length > 0
218
+
219
+ #*
220
+ # Specifies whether or not there are any filterable items, either in this
221
+ # `Filterable` or in any nested `Filterable`s, which are still visible
222
+ # based on the active filters.
223
+ #
224
+ # @property
225
+ # @type Boolean
226
+ has_visible_items: get: ->
227
+ items.some((item) -> item.is_visible) ||
228
+ groups.some((group) -> group.has_visible_items)
229
+
230
+ #*
231
+ # Applies a filter against all contained items and nested `Filterables`.
232
+ # Once the filter has been applied, this method also sets the current
233
+ # filtered state in the `data-filter-status` on the
234
+ # [`root`](@link Filterable#root) node.
235
+ #
236
+ # @method
237
+ #
238
+ # @param {Filter} filter - The filter against which all contained items
239
+ # should be compared.
240
+ filter_against: value: (filter) ->
241
+ group.filter_against(filter) for group in groups
242
+ item.filter_against(filter) for item in items
243
+ root.setAttribute(ATTRS.STATUS, if @has_visible_items then STATUS.VISIBLE else STATUS.HIDDEN)
244
+ return
245
+
246
+ $(root).data("filterable", api) unless options.nested
247
+ api
248
+
249
+ #*
250
+ # Returns the cached [`Filterable`](@link) for the passed node, or nothing if
251
+ # the cached node is not the *root* of a `Filterable`.
252
+ #
253
+ # @method
254
+ # @static
255
+ #
256
+ # @param {HTMLElement} node - The node for which a `Filterable` should be
257
+ # retrieved.
258
+ #
259
+ # @returns Filterable
260
+
261
+ Filterable.for = (node) -> $(node).data("filterable")
262
+
263
+ #*
264
+ # @factory
265
+ #
266
+ # @param {HTMLElement} node - The `input` that filters some set of
267
+ # [`Filterable`s](@link Filterable). This node **must** have:
268
+ #
269
+ # - A `data-filters` attribute, specifying selectors that are the root of
270
+ # a set of filterable items.
271
+ #
272
+ # - A `data-filter-attribute` attribute, specyfing the attribute of filterable
273
+ # items inside the [`Filterable`](@link) that should be compared against this
274
+ # filter. This can either be an attribute on all filterable items (i.e.,
275
+ # the private member toggle in the base theme compates against `data-private`)
276
+ # or the special word `"content"`, which will compare against a filterable
277
+ # item's `textContent` property.
278
+
279
+ Filter = do ->
280
+ filter_id = 1
281
+
282
+ matcher_for = (node) ->
283
+ if node.type == "checkbox"
284
+ if node.checked
285
+ value = node.getAttribute(ATTRS.CHECKED_VALUE)
286
+ if value then new RegExp(value) else /true/
287
+ else
288
+ value = node.getAttribute(ATTRS.UNCHECKED_VALUE)
289
+ if value then new RegExp(value) else /false/
290
+
291
+ else
292
+ value = node.value.split("")
293
+ if value.length == 0 then { test: -> true } else new RegExp(value.join(".*?"), "i")
294
+
295
+ filterables_for = (node) ->
296
+ filter_selectors = node.getAttribute(ATTRS.FILTERS).split(/\s*,\s*/)
297
+
298
+ filterables = Array::reduce.call filter_selectors, (objects, selector) ->
299
+ filterable = Filterable.for(document.querySelector(selector))
300
+ # What if not filterable'd?
301
+ objects.push(filterable) if filterable && filterable.is_valid
302
+ objects
303
+ , []
304
+
305
+ (node) ->
306
+ matcher = null
307
+ # Set a default?
308
+ filters_on = node.getAttribute(ATTRS.ATTRIBUTE)
309
+ filters_on_contents = filters_on == "contents"
310
+ filterables = filterables_for(node)
311
+
312
+ api =
313
+ #*
314
+ # The unique identifier for the filter. This is used for each
315
+ # [`FilterableItem`'s](@link FilterableItem) to create an internal list
316
+ # of all attached filters and whether or not they currently match.
317
+ #
318
+ # @property
319
+ # @type String
320
+ id: node.id || "filter-#{filter_id++}"
321
+
322
+ #*
323
+ # Tests a node against this `Filter`. If the method returns `true`, then
324
+ # the item passes the filter and should be shown (assuming no other
325
+ # filters are not passed by the item). If it returns `false`, the item
326
+ # should be hidden.
327
+ #
328
+ # @method
329
+ # @param {FilterableItem} item - The filterable item to test against this
330
+ # filter.
331
+ #
332
+ # @returns Boolean
333
+ test: (item) ->
334
+ matcher ||= matcher_for(node)
335
+ compare = if filters_on_contents then item.node.textContent else item.node.getAttribute(filters_on)
336
+ matches = matcher.test(compare)
337
+ highlight_match(item.node) if filters_on_contents && matches
338
+ matches
339
+
340
+ highlight_match = (item) ->
341
+ item.innerHTML = highlight_characters(item.textContent, node.value)
342
+
343
+ update = ->
344
+ matcher = null
345
+ filterable.filter_against(api) for filterable in filterables
346
+ return
347
+
348
+ $(node).on("change input", update)
349
+ update()
350
+ api
351
+
352
+ Lemon.make(Filterable, selector: "[#{ATTRS.ITEMS}]")
353
+ Lemon.make(Filter, selector: "[#{ATTRS.FILTERS}]")
File without changes
@@ -0,0 +1,34 @@
1
+ //*
2
+ // This mixin allows you to specify custom styling for items that should be
3
+ // hidden based on the attached filters. By default, filtered items are set to
4
+ // `display: none;` — use this mixin to provide custom styles.
5
+ //
6
+ // Note that this mixin can't be used at the root level — it must be nested
7
+ // under another selector.
8
+ //
9
+ // @example
10
+ // .foo {
11
+ // @include when-filtered {
12
+ // background: red;
13
+ // }
14
+ // }
15
+ //
16
+ // // yields:
17
+ //
18
+ // .foo[data-filtered-status = "hidden"] {
19
+ // display: block !important; // overrides the default hidden state
20
+ // background: red;
21
+ // }
22
+
23
+ @mixin when-filtered {
24
+ &[data-filtered-status = "hidden"] {
25
+ display: block !important;
26
+ @content;
27
+ }
28
+ }
29
+
30
+ .filter--highlight {
31
+ background-color: color(yellow);
32
+ }
33
+
34
+ [data-filtered-status = "hidden"] { display: none !important; }
@@ -0,0 +1,3 @@
1
+ {
2
+ "main": "filterable.js"
3
+ }
File without changes
@@ -0,0 +1,20 @@
1
+ <%
2
+ avatar.configure do |config|
3
+ config.defaults(author: OpenStruct.new(name: "", github: "", twitter: "", email: ""))
4
+ end
5
+
6
+ service, profile_name, url = if avatar.author.github
7
+ ["github", avatar.author.github, "https://github.com/#{avatar.author.github}"]
8
+ elsif avatar.author.twitter
9
+ ["twitter", avatar.author.twitter, "https://twitter.com/#{avatar.author.twitter}"]
10
+ elsif avatar.author.email
11
+ ["email", avatar.author.email, "mailto:#{avatar.author.email}"]
12
+ else
13
+ []
14
+ end
15
+ %>
16
+
17
+ <a class="avatar" data-service="<%= service %>" data-profile-name="<%= profile_name %>" href="<%= url.nil? ? "javascript:;" : url %>">
18
+ <span class="avatar__initials"><%= avatar.author.name.split(/\s+/).map { |name| name[0] }.join("") %></span>
19
+ <div class="avatar__image"></div>
20
+ </a>
@@ -0,0 +1,142 @@
1
+ // ___ ___ ___ ___
2
+ // / /\ ___ / /\ ___ / /\ / /\
3
+ // / /::\ /__/\ / /::\ / /\ / /::\ / /::\
4
+ // / /:/\:\ \ \:\ / /:/\:\ / /:/ / /:/\:\ / /:/\:\
5
+ // / /:/~/::\ \ \:\ / /:/~/::\ / /:/ / /:/~/::\ / /:/~/:/
6
+ // /__/:/ /:/\:\___ \__\:\/__/:/ /:/\:\/ /::\/__/:/ /:/\:\/__/:/ /:/___
7
+ // \ \:\/:/__\/__/\ | |:|\ \:\/:/__\/__/:/\:\ \:\/:/__\/\ \:\/:::::/
8
+ // \ \::/ \ \:\| |:| \ \::/ \__\/ \:\ \::/ \ \::/~~~~
9
+ // \ \:\ \ \:\__|:| \ \:\ \ \:\ \:\ \ \:\
10
+ // \ \:\ \__\::::/ \ \:\ \__\/\ \:\ \ \:\
11
+ // \__\/ ~~~~ \__\/ \__\/ \__\/
12
+
13
+ import Builder from "~utilities/builder";
14
+ import "whatwg-fetch";
15
+
16
+ //*
17
+ // The name of classes relevant to `Avatar`.
18
+ // @object
19
+
20
+ const classes = {
21
+ //*
22
+ // @property
23
+ root: "avatar",
24
+
25
+ //*
26
+ // @property
27
+ image: "avatar__image",
28
+
29
+ //*
30
+ // @property
31
+ initials: "avatar__initials"
32
+ };
33
+
34
+ //*
35
+ // The name of states relevant to `Avatar`.
36
+ // @object
37
+
38
+ const states = {
39
+ //*
40
+ // @property
41
+ image: {
42
+ visible: `${classes.image}--is-visible`
43
+ }
44
+ };
45
+
46
+ //*
47
+ // The name of attributes relevant to `Avatar`.
48
+ // @object
49
+
50
+ const attrs = {
51
+ //*
52
+ // @property
53
+ profile_name: "data-profile-name",
54
+
55
+ //*
56
+ // @property
57
+ service: "data-service"
58
+ };
59
+
60
+ //*
61
+ // The minimum time, in milliseconds, before the background images for avatars
62
+ // should be faded into view. This is done to prevent any sudden visual changes
63
+ // immediately after page load.
64
+ //
65
+ // @value 200
66
+ // @type Number
67
+ // @private
68
+
69
+ const MIN_TIME_TO_LOAD = 200;
70
+
71
+ var Avatar, show_image;
72
+
73
+ //*
74
+ // Fades the image into view smoothly. To prevent sudden appearance of images
75
+ // immediately after page load, this function stores the time when it was
76
+ // initialized and waits at least `MIN_TIME_TO_LOAD` after that before applying
77
+ // the required classes.
78
+ //
79
+ // @private
80
+ // @param {DOMElement} image - The image to reveal.
81
+
82
+ show_image = (() => {
83
+ var start = Date.now();
84
+
85
+ return (image) => {
86
+ setTimeout(() => {
87
+ image.classList.add(states.image.visible);
88
+ }, Math.max(0, MIN_TIME_TO_LOAD - (Date.now() - start)));
89
+ };
90
+ })();
91
+
92
+ //*
93
+ // The constructor around an avatar DOM node. This constructor will check for
94
+ // the service from which the avatar image should be fetched and do its best to
95
+ // grab that image.
96
+ //
97
+ // Because there is no way to interact with an `Avatar`, there is no public
98
+ // interface for this component.
99
+ //
100
+ // @factory
101
+ //
102
+ // @param {DOMElement} node - The root of an Avatar component.
103
+
104
+ Avatar = (node) => {
105
+ var profile_name = node.getAttribute(attrs.profile_name),
106
+ image = node.querySelector(`.${classes.image}`),
107
+ service = node.getAttribute(attrs.service);
108
+
109
+ switch(service) {
110
+ case "github":
111
+ fetch(`https://api.github.com/users/${profile_name}`)
112
+ .then((response) => { return response.json(); })
113
+ .then((response) => {
114
+ image.style.backgroundImage = `url(${response.avatar_url})`;
115
+ show_image(image);
116
+ });
117
+ break;
118
+
119
+ case "twitter":
120
+ case "email":
121
+ image.style.backgroundImage = `url(http://avatars.io/${service}/${profile_name})`;
122
+ show_image(image);
123
+ break;
124
+ }
125
+ };
126
+
127
+ //*
128
+ // Initializes the `Avatar` component.
129
+ //
130
+ // @method
131
+ // @static
132
+ //
133
+ // @requires builder::Builder
134
+ //
135
+ // @arg {HTMLElement} [context = document] - The context in which to search
136
+ // for DOM nodes that should be used as the root of an [`Avatar`](@link)
137
+ // component.
138
+
139
+ Avatar.init = Builder.initialize_once(Avatar, { name: classes.root });
140
+
141
+ export { classes, states, attrs };
142
+ export default Avatar;