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,236 @@
1
+ // ___ ___
2
+ // ___ / /\ _____ / /\
3
+ // / /\ / /::\ / /::\ / /:/_
4
+ // / /:/ / /:/\:\ / /:/\:\ ___ ___ / /:/ /\
5
+ // / /:/ / /:/~/::\ / /:/~/::\/__/\ / /\/ /:/ /:/_
6
+ // / /::\/__/:/ /:/\:\/__/:/ /:/\:\ \:\ / /:/__/:/ /:/ /\
7
+ // /__/:/\:\ \:\/:/__\/\ \:\/:/~/:/\ \:\ /:/\ \:\/:/ /:/
8
+ // \__\/ \:\ \::/ \ \::/ /:/ \ \:\/:/ \ \::/ /:/
9
+ // \ \:\ \:\ \ \:\/:/ \ \::/ \ \:\/:/
10
+ // \__\/\ \:\ \ \::/ \__\/ \ \::/
11
+ // \__\/ \__\/ \__\/
12
+
13
+ //*
14
+ // @pattern Table
15
+ //
16
+ // Tables are used to present tabular data only — never for layout! These tables
17
+ // are smarter than the average table, however. They will determine their
18
+ // intrinsic size (up to a stylesheet-specified maximum) and, when the space
19
+ // available to them is less than that size, they will begin to scroll.
20
+ //
21
+ // As you scroll, the first column will stay fixed, so you always have context,
22
+ // and the any given column will always be small enough to be fully visible. Not
23
+ // only that, but the table provides both keyboard input and interface elements
24
+ // that allow you to quickly shift to overscrolled columns.
25
+ //
26
+ // @since 1.0.0
27
+
28
+
29
+
30
+ $table--border: 1px solid color(gray-light);
31
+ $table--border-radius: double(default(border-radius));
32
+ $table--box-shadow-size: 3px 0 4px -1px;
33
+
34
+
35
+
36
+ //*
37
+ // A smarter `table` element. The `min-width` placed here will not actually
38
+ // be the minimum width of the rendered table; the provided minimum width will
39
+ // instead be used as the maximum width allowed in determining the preferred/
40
+ // intrinsic size of the table cells. That is, a table whose natural size is
41
+ // less than this amount will not overflow until the space it has is less than
42
+ // its intrinsic size, where a table with large cells will overflow at the
43
+ // provided `min-width` breakpoint.
44
+ //
45
+ // Visually, all tables will be 100% width. As noted above, tables will overflow
46
+ // once the lesser of the `min-width` below or the intrisic size of the table
47
+ // is reached. This overflowing is managed by the JavaScript component.
48
+ //
49
+ // @helper docks_table
50
+
51
+ .table {
52
+ width: 100%;
53
+ min-width: rem(500);
54
+
55
+ border-collapse: separate;
56
+ border-spacing: 0;
57
+ }
58
+
59
+
60
+
61
+ //*
62
+ // The outermost wrapper around a table. This wrapper is used to signal to the
63
+ // entire component when the table is in overflow (via the
64
+ // `table__container--is-overflowing` state). Doing so on the outmost container
65
+ // allows adjustments to both the table itself and the `table__actions`.
66
+ //
67
+ // In addition, `table__containers` will automatically create the required
68
+ // space between themselves and other content.
69
+
70
+ .table__container {
71
+ margin: default(spacing) auto;
72
+ }
73
+
74
+ //*
75
+ // Signals to the `table` that it should force the first cell in each row
76
+ // to float about the rest of the rows (the required left padding on the
77
+ // second cell in each row to account for the absolutely-positioned first cell
78
+ // is done automatically by the JavaScript side of things, as is the
79
+ // equalization of all of the first cells' widths). Additionally, this
80
+ // state causes the `table__actions` to be made visible.
81
+ //
82
+ // @set_by Table#check_for_overflow
83
+ // @demo_type none
84
+
85
+ .table__container--is-overflowing {
86
+ .table__cell:first-child {
87
+ position: absolute;
88
+ @include z-index(cell-persistant, table);
89
+ left: 0;
90
+
91
+ height: 100%;
92
+
93
+ // Start box shadow as transparent so it can be animated into view when
94
+ // the table is actually being scrolled.
95
+ box-shadow: $table--box-shadow-size rgba(color(black), 0);
96
+ transition: box-shadow 0.2s ease;
97
+ }
98
+
99
+ .table__actions {
100
+ transform: translateY(0);
101
+ max-height: 3rem;
102
+ padding-bottom: half(default(spacing));
103
+ }
104
+ }
105
+
106
+
107
+
108
+ //*
109
+ // A wrapper around the `table` that allows it to scroll when the minimum
110
+ // width of its columns (their intrinsic width or their width when the table
111
+ // is larger than the `min-width` set on `table`) is larger than the space
112
+ // available to this container.
113
+
114
+ .table__scroller {
115
+ max-width: 100%;
116
+ overflow-x: auto;
117
+ overflow-y: hidden;
118
+ -webkit-overflow-scrolling: touch;
119
+ }
120
+
121
+ //*
122
+ // The state that is added by the JavaScript component when the table is
123
+ // overflowed **and** the scroll position of this subcomponent is not fully
124
+ // pinned to the left.
125
+ //
126
+ // @set_by Table#handle_scroll, Table#shift_table
127
+
128
+ .table__scroller--is-scrolled {
129
+ .table__cell:first-child {
130
+ box-shadow: $table--box-shadow-size rgba(color(black), 0.1);
131
+ }
132
+ }
133
+
134
+
135
+
136
+ //*
137
+ // The wrapper around a `table` that provides a few visual pieces. Most
138
+ // noticeably, this adds the border and border radius on the outside of the
139
+ // table. These pieces must be on this container rather than the
140
+ // `table__container` subcomponent because it should only wrap around the
141
+ // `table`, and not the `table__actions`. These rules can't be on the
142
+ // `table__scroller` subcomponent, either, because it must be `overflow: hidden`
143
+ // (to hide the parts outside the rounded corners), where the scroller must
144
+ // be allowed to scroll.
145
+ //
146
+ // More importantly, however, this container is `position: relative` and, as
147
+ // such, acts as the element against which the persistant cells are
148
+ // positioned. These rules can't be on the `table__scroller` because it scrolls,
149
+ // where the persistant cells should be fixed even as the rest of the table
150
+ // scrolls.
151
+
152
+ .table__backdrop {
153
+ // position
154
+ position: relative;
155
+ @include z-index(backdrop, table);
156
+
157
+ // box model
158
+ max-width: 100%;
159
+ clear: both;
160
+
161
+ // backdrop
162
+ overflow: hidden;
163
+ border: $table--border;
164
+ border-radius: $table--border-radius;
165
+ }
166
+
167
+
168
+
169
+ //*
170
+ // A container around the actions that can be performed on the `table`. For now,
171
+ // this includes only the segmented button to shift the table right/ left by
172
+ // one column.
173
+
174
+ .table__actions {
175
+ transform: translateY(140%);
176
+ max-height: 0;
177
+ float: right;
178
+
179
+ transition: transform 0.3s $bouncy-transition,
180
+ max-height 0.3s ease,
181
+ padding-bottom 0.3s ease;
182
+ }
183
+
184
+
185
+
186
+ //*
187
+ // The header row of the table.
188
+
189
+ .table__header {
190
+ font-weight: 400;
191
+ padding: 0;
192
+ text-align: left;
193
+ }
194
+
195
+
196
+
197
+ //*
198
+ // The container around the rows that make up the body of the table.
199
+
200
+ .table__body {
201
+ // Striped rows!
202
+ > .table__row:nth-child(odd) > .table__cell {
203
+ background-color: color(gray-lighter);
204
+ }
205
+ }
206
+
207
+
208
+
209
+ //*
210
+ // A row of table cells.
211
+
212
+ .table__row {
213
+ // Hide the overflow so that box shadows on persistant cells don't bleed
214
+ // between rows.
215
+ overflow: hidden;
216
+ }
217
+
218
+
219
+
220
+ //*
221
+ // An individual table cell.
222
+
223
+ .table__cell {
224
+ position: relative;
225
+ @include z-index(cell, table);
226
+ vertical-align: top;
227
+ padding: half(default(spacing));
228
+ background-color: color(white);
229
+ }
230
+
231
+ //*
232
+ // A table cell with center-aligned text.
233
+
234
+ .table__cell--centered {
235
+ text-align: center;
236
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "main": "tablist.js"
3
+ }
@@ -0,0 +1,13 @@
1
+ <%
2
+ tablist.configure do |config|
3
+ config.defaults(manage_url?: false, size: :regular)
4
+ config.classes(base: "tablist")
5
+
6
+ config.conditional_classes(from: :size, large: { base: "tablist--large" })
7
+ config.conditional_classes(if: :manage_url?, base: "tablist--manages-url")
8
+ end
9
+ %>
10
+
11
+ <div class="<%= tablist.classes_for(:base) %>" role="tablist">
12
+ <%= capture(tablist, &tablist.block) %>
13
+ </div>
@@ -0,0 +1,246 @@
1
+ import QueryString from "~utilities/query_string";
2
+ import Builder from "~utilities/builder";
3
+ import Cache from "~utilities/dom_cache";
4
+
5
+ const classes = {
6
+ root: "tablist",
7
+ tab: "tablist__tab",
8
+ panel: "tablist__panel"
9
+ };
10
+
11
+ const variants = {
12
+ root: { manages_url: "tablist--manages-url" }
13
+ };
14
+
15
+ const states = {
16
+ tab: { active: "tablist__tab--is-active" },
17
+ panel: { active: "tablist__panel--is-active" }
18
+ };
19
+
20
+ var Tablist, tab_click, panel_for_tab, tab_for_panel, tablist_for_node, a11y,
21
+ apply_activation_markup, remove_activation_markup, panel_containing_node;
22
+
23
+ //*
24
+ // Manages a click on a tab by finding the associated `Tablist` and activating
25
+ // the tab that was clicked on.
26
+ //
27
+ // @param {Object} event - The `click` event.
28
+ // @private
29
+
30
+ tab_click = (event) => {
31
+ var tablist;
32
+
33
+ event.preventDefault();
34
+
35
+ tablist = Tablist.for(event.target);
36
+ if(!tablist) { return; }
37
+ tablist.activate_tab($(event.currentTarget).closest(`.${classes.tab}`)[0]);
38
+ };
39
+
40
+ //*
41
+ // Finds the tab panel associated with the passed tab. The association is based
42
+ // on the ID of the tab panel matching the `href` of the tab.
43
+ //
44
+ // @param {HTMLElement} tab - The tab for which you want the associated panel.
45
+ // @private
46
+ //
47
+ // @returns {HTMLElement | null} The associated tab panel or, if no matching
48
+ // panel was found, `null`.
49
+
50
+ panel_for_tab = (tab) => {
51
+ return tab && document.getElementById(tab.getAttribute("href").replace("#", ""));
52
+ };
53
+
54
+ //*
55
+ // Finds the tab associated with the passed panel. The association is based
56
+ // on the ID of the tab panel matching the `href` of the tab.
57
+ //
58
+ // @param {HTMLElement} tab - The tab for which you want the associated panel.
59
+ // @private
60
+ //
61
+ // @returns {HTMLElement | null} The associated tab or, if no matching panel
62
+ // was found, `null`.
63
+
64
+ tab_for_panel = (panel) => {
65
+ return panel && document.querySelector(`.${classes.tab}[href='#${panel.id}']`);
66
+ };
67
+
68
+ //*
69
+ // Writes all of the required accessibility markup to the tablist and its
70
+ // subcomponents. This includes IDs for the tablist and its tabs/ panels,
71
+ // roles for the same, and the `aria-` associations between tabs and their
72
+ // corresponding panels.
73
+ //
74
+ // @param {HTMLElement} tablist - The root node of the tablist.
75
+ // @private
76
+
77
+ a11y = (() => {
78
+ var current_ids, id_for;
79
+
80
+ current_ids = {
81
+ [classes.root]: 1,
82
+ [classes.tab]: 1,
83
+ [classes.panel]: 1
84
+ };
85
+
86
+ id_for = (node) => {
87
+ var type = node.className.split(" ")[0];
88
+ return `${type}--${current_ids[type]++}`;
89
+ };
90
+
91
+ return (tablist) => {
92
+ var panel, tab_id, panel_id, tab;
93
+
94
+ tablist.id = tablist.id || id_for(tablist);
95
+ tablist.setAttribute("role", "tablist");
96
+
97
+ for(tab of Array.from(tablist.querySelectorAll(`.${classes.tab}`))) {
98
+ panel = panel_for_tab(tab);
99
+ if(!panel) { continue; }
100
+
101
+ tab_id = tab.id || id_for(tab);
102
+ panel_id = panel.id || id_for(panel);
103
+
104
+ tab.id = tab_id;
105
+ tab.setAttribute("role", "tab");
106
+ tab.setAttribute("aria-controls", panel_id);
107
+ tab.setAttribute("href", `#${panel_id}`);
108
+
109
+ panel.id = panel_id;
110
+ panel.setAttribute("role", "tab-panel");
111
+ panel.setAttribute("aria-labelledby", tab_id);
112
+ panel.setAttribute("aria-hidden", !panel.classList.contains(states.panel.active));
113
+ }
114
+ };
115
+ })();
116
+
117
+ apply_activation_markup = (node) => {
118
+ if(!node) { return; }
119
+
120
+ if(node.classList.contains(classes.tab)) {
121
+ node.classList.add(states.tab.active);
122
+ } else {
123
+ node.classList.add(states.panel.active);
124
+ }
125
+ };
126
+
127
+ remove_activation_markup = (node) => {
128
+ if(!node) { return; }
129
+
130
+ if(node.classList.contains(classes.tab)) {
131
+ node.classList.remove(states.tab.active);
132
+ } else {
133
+ node.classList.remove(states.panel.active);
134
+ }
135
+ };
136
+
137
+ panel_containing_node = (node) => {
138
+ return $(node).closest(`.${classes.panel}`)[0];
139
+ };
140
+
141
+ tablist_for_node = (node) => {
142
+ if(node.classList.contains(classes.panel)) {
143
+ node = tab_for_panel(node);
144
+ }
145
+
146
+ return $(node).closest(`.${classes.root}`)[0];
147
+ };
148
+
149
+ //*
150
+ // The constructor around a `Tablist` component. This constructor returns a very
151
+ // small API: only an `activate_tab` method is exposed, which will activate the
152
+ // passed tab in the tablist. This constructor will also ensure that all the
153
+ // aria properties and associations are hooked up correctly.
154
+
155
+ Tablist = (root) => {
156
+ var active_tab = root.querySelector(`.${states.tab.active}`),
157
+ active_panel = panel_for_tab(active_tab),
158
+ saved_tab, api, self;
159
+
160
+ a11y(root);
161
+
162
+ self = {
163
+ root,
164
+ id: root.id,
165
+ active_panel: panel_for_tab(active_tab),
166
+ manages_url: root.classList.contains(variants.root.manages_url)
167
+ };
168
+
169
+ api = {
170
+ //*
171
+ // Activates the passed tab, deactivating the currently-active tab, if there
172
+ // is one (and it is not the passed tab).
173
+ //
174
+ // @param {HTMLElement} tab - The tab to activate.
175
+
176
+ activate_tab(tab) { this.active_tab = tab; },
177
+
178
+ get active_tab() { return active_tab; },
179
+ set active_tab(tab) {
180
+ var panel = panel_for_tab(tab);
181
+
182
+ apply_activation_markup(tab);
183
+ apply_activation_markup(panel);
184
+
185
+ if(!tab || tab === active_tab) { return; }
186
+
187
+ remove_activation_markup(active_tab);
188
+ remove_activation_markup(active_panel);
189
+
190
+ active_tab = tab;
191
+ active_panel = panel;
192
+
193
+ if(this.manages_url && QueryString.get(this.id) !== tab.id) {
194
+ QueryString.set(this.id, tab.id);
195
+ }
196
+ },
197
+
198
+ get active_panel() { return active_panel; },
199
+ set active_panel(panel) {
200
+ this.active_tab = panel_for_tab(panel);
201
+ }
202
+ };
203
+
204
+ if(self.manages_url) {
205
+ saved_tab = QueryString.get(self.id);
206
+ if(saved_tab) { api.active_tab = document.getElementById(saved_tab); }
207
+ } else {
208
+ api.active_tab = active_tab;
209
+ }
210
+
211
+ return api;
212
+ };
213
+
214
+ Tablist.for = (node) => {
215
+ var tablist_node = $(node).closest(`.${classes.root}`)[0],
216
+ containing_panel;
217
+
218
+ if(!tablist_node) {
219
+ containing_panel = node.classList.contains(classes.panel) ? node : panel_containing_node(node);
220
+ if(!containing_panel) { return false; }
221
+ tablist_node = tablist_for_node(containing_panel);
222
+ }
223
+
224
+ if(!tablist_node) { return false; }
225
+ return Cache(tablist_node).get(classes.root);
226
+ };
227
+
228
+ Tablist.init = () => {
229
+ Builder.build_and_cache(Tablist, { name: classes.root });
230
+ $(document).on("click", `.${classes.tab}`, tab_click);
231
+ };
232
+
233
+ Tablist.activate_panel_containing = (node) => {
234
+ var panel = $(node).closest(`.${classes.panel}`)[0],
235
+ tablist = Tablist.for(panel);
236
+
237
+ if(tablist) { tablist.active_tab = tab_for_panel(panel); }
238
+ return !!tablist;
239
+ };
240
+
241
+ Tablist.is_in_active_panel = (node) => {
242
+ var panel = panel_containing_node(node);
243
+ return !!panel && panel.classList.contains(states.panel.active);
244
+ };
245
+
246
+ export default Tablist;
@@ -0,0 +1,191 @@
1
+ // ___ ___
2
+ // ___ / /\ _____ ___ / /\ ___
3
+ // / /\ / /::\ / /::\ / /\ / /:/_ / /\
4
+ // / /:/ / /:/\:\ / /:/\:\ ___ ___ / /:/ / /:/ /\ / /:/
5
+ // / /:/ / /:/~/::\ / /:/~/::\/__/\ / /\/__/::\ / /:/ /::\ / /:/
6
+ // / /::\/__/:/ /:/\:\/__/:/ /:/\:\ \:\ / /:/\__\/\:\__/__/:/ /:/\:\/ /::\
7
+ // /__/:/\:\ \:\/:/__\/\ \:\/:/~/:/\ \:\ /:/ \ \:\/\ \:\/:/~/:/__/:/\:\
8
+ // \__\/ \:\ \::/ \ \::/ /:/ \ \:\/:/ \__\::/\ \::/ /:/\__\/ \:\
9
+ // \ \:\ \:\ \ \:\/:/ \ \::/ /__/:/ \__\/ /:/ \ \:\
10
+ // \__\/\ \:\ \ \::/ \__\/ \__\/ /__/:/ \__\/
11
+ // \__\/ \__\/ \__\/
12
+
13
+ //*
14
+ // @pattern Tablist
15
+ //
16
+ // Tablists are used to create groups of related content that is conditionally
17
+ // shown or hidden based on the selected tab. The JavaScript part of this
18
+ // components ensures that the required accessibility-related markup is added
19
+ // to the tabs, and a number of different styles are available depending on
20
+ // the context in which the tabs are being used.
21
+ //
22
+ // @since 1.0.0
23
+
24
+ //*
25
+ // The container for a set of tabs. While panels and tabs both use `tablist`
26
+ // as their base component, only `tablist__tab`s are child nodes of this
27
+ // component; `tablist__panel`s (and their container) are separate from the
28
+ // document to allow tabs to move around the page independently of the content
29
+ // they actually activate.
30
+ //
31
+ // The `tablist--manages-url` variant is unique in that it has no visual
32
+ // differences, but will preserve the selected tab via a query string parameter
33
+ // and restore the selected tab on subsequent page loads. To add this
34
+ // functionality, simply pass `true` for the `:manage_url` option of the
35
+ // `docks_tablist` view helper.
36
+ //
37
+ // @helper docks_tablist
38
+
39
+ .tablist {
40
+ // box model
41
+ margin: 0;
42
+ padding: 0;
43
+ display: flex;
44
+ justify-content: center;
45
+ padding: half(default(spacing));
46
+ margin-right: negative(half(default(spacing)));
47
+
48
+ // backdrop
49
+ list-style: none;
50
+ }
51
+
52
+ //*
53
+ // A larger set of tabs (both in terms of the font size of the tabs and the
54
+ // padding within each tab).
55
+ //
56
+ // @set_by :size (:large)
57
+
58
+ .tablist--large {
59
+ margin: double(default(spacing)) 0;
60
+
61
+ > .tablist__tab {
62
+ @include font-size(tablist--large);
63
+ line-height: 1;
64
+ padding: default(spacing) multiply(default(spacing), 2.5) multiply(default(spacing), 1.2);
65
+ border: none !important;
66
+ opacity: 1 !important;
67
+ }
68
+
69
+ > .tablist__tab--is-active {
70
+ background-color: ui-color(gray, light);
71
+ }
72
+ }
73
+
74
+ //*
75
+ // A variation of the tablist that forces the JavaScript to preserve the
76
+ // selected tab and reload that tab on subsequent page loads.
77
+ //
78
+ // @demo_type none
79
+ // @set_by :manage_url
80
+
81
+ .tablist--manages-url {}
82
+
83
+
84
+
85
+
86
+ //*
87
+ // A single tab in the tablist. Tabs will be a relatively light color by
88
+ // default with progressively darker shades used on `:focus`/`:hover` and when
89
+ // `:active`.
90
+ //
91
+ // Tabs should still work even when there is no JavaScript or CSS. To make this
92
+ // work, ensure that each `tablist__tab` is an `a` tag with an `href` that
93
+ // points to the `id` of the relevant `tablist__panel`. Likewise, the `id`s of
94
+ // the tab and panel should match up vai the `aria-controls` and
95
+ // `aria-labelledby` properties. The JavaScript component, if run, will
96
+ // guarantee all of these associations.
97
+ //
98
+ // @helper docks_tablist_tab
99
+
100
+ .tablist__tab {
101
+ // box model
102
+ display: inline-block;
103
+ padding: 0 default(control-padding);
104
+
105
+ // backdrop
106
+ opacity: 0.3;
107
+ overflow: hidden;
108
+ border: 1px solid transparent;
109
+ border-radius: default(border-radius);
110
+ transition: opacity 0.3s ease, border-color 0.3s ease;
111
+
112
+ // type
113
+ @include font-size(control);
114
+ line-height: default(control-size);
115
+ color: ui-color(gray, darker);
116
+ text-align: center;
117
+ text-decoration: none;
118
+ white-space: nowrap;
119
+ text-overflow: ellipsis;
120
+
121
+ &:focus,
122
+ &:hover {
123
+ outline: none;
124
+ opacity: 0.5;
125
+ }
126
+
127
+ > .icon {
128
+ height: double(default(spacing));
129
+ width: double(default(spacing));
130
+ @include icon--recolor(gray, dark);
131
+ stroke-width: 3px;
132
+ }
133
+ }
134
+
135
+ //*
136
+ // The variation added to the tab whose corresponding tab is currently
137
+ // visible. This class can either be applied manually by setting the `:active?`
138
+ // argument to the helper method to `true` or by clicking on the correct tab
139
+ // (which does all the required attribute manipulations through the JavaScript
140
+ // component).
141
+ //
142
+ // @demo_type none
143
+ // @set_by :active?
144
+
145
+ .tablist__tab--is-active {
146
+ &, &:hover, &:focus {
147
+ border-color: ui-color(gray, darker);
148
+ opacity: 1;
149
+ }
150
+ }
151
+
152
+ //*
153
+ // The text within a tab. This container is required in order for the text and
154
+ // (optional) icon to be above one another.
155
+
156
+ .tablist__tab__text {
157
+ display: block;
158
+ }
159
+
160
+
161
+
162
+ //*
163
+ // A panel that contains the content that should be shown when the associated
164
+ // tab is active.
165
+ //
166
+ // @helper docks_tablist_panel
167
+
168
+ .tablist__panel {
169
+ display: none;
170
+ }
171
+
172
+ //*
173
+ // A state indicating that the associated tab is active and the contents of
174
+ // this panel should be made visible. There is no animation when switching
175
+ // between tabs — they simply pop in and out of view.
176
+ //
177
+ // @demo_type none
178
+ // @set_by :active?
179
+
180
+ .tablist__panel--is-active {
181
+ display: block;
182
+ }
183
+
184
+
185
+
186
+ //*
187
+ // A container around all of the panels for a tablist. This container
188
+ // technically does nothing, but is a nice way to isolate a set of tab panels
189
+ // from the surrounding content.
190
+
191
+ .tablist__panel-container {}
@@ -0,0 +1,14 @@
1
+ <%
2
+ panel.configure do |config|
3
+ config.defaults active?: false,
4
+ id: "panel",
5
+ tab_id: "tab"
6
+
7
+ config.classes(base: "tablist__panel")
8
+ config.conditional_classes(if: :active?, base: "tablist__panel--is-active")
9
+ end
10
+ %>
11
+
12
+ <div class="<%= panel.classes_for(:base) %>" role="tab-panel" id="<%= panel.id %>" aria-hidden="<%= !panel.active? %>" aria-labelledby="<%= panel.tab_id %>">
13
+ <%= capture(panel, &panel.block) %>
14
+ </div>