docks_theme_api 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.babelrc +4 -0
- data/.editorconfig +8 -0
- data/.eslintrc +115 -0
- data/.gitignore +24 -0
- data/.rubocop.yml +20 -0
- data/.travis.yml +16 -0
- data/Gemfile +4 -0
- data/README.md +5 -0
- data/Rakefile +3 -0
- data/assets/images/icons.svg +63 -0
- data/assets/scripts/coffeescript/pattern_library_helpers.coffee +8 -0
- data/assets/scripts/javascript/pattern_library_helpers.js +11 -0
- data/assets/scripts/pattern_library.js +10380 -0
- data/assets/scripts/pattern_library_demo.js +0 -0
- data/assets/styles/less/pattern-library-helpers.less +103 -0
- data/assets/styles/pattern-library-demo.css +1882 -0
- data/assets/styles/pattern-library.css +1882 -0
- data/assets/styles/sass/pattern-library-helpers.sass +90 -0
- data/assets/styles/scss/pattern-library-helpers.scss +99 -0
- data/assets/styles/stylus/pattern-library-helpers.styl +90 -0
- data/assets/templates/erb/demo.erb +26 -0
- data/assets/templates/erb/layouts/demo.erb +17 -0
- data/assets/templates/erb/layouts/pattern.erb +76 -0
- data/assets/templates/erb/partials/sidebar.erb +124 -0
- data/assets/templates/erb/partials/symbols/class.erb +1 -0
- data/assets/templates/erb/partials/symbols/demo.erb +40 -0
- data/assets/templates/erb/partials/symbols/factory.erb +70 -0
- data/assets/templates/erb/partials/symbols/function.erb +103 -0
- data/assets/templates/erb/partials/symbols/mixin.erb +62 -0
- data/assets/templates/erb/partials/symbols/variable.erb +59 -0
- data/assets/templates/erb/pattern.erb +102 -0
- data/assets/templates/haml/demo.haml +14 -0
- data/assets/templates/haml/layouts/demo.haml +6 -0
- data/assets/templates/haml/layouts/pattern.haml +38 -0
- data/assets/templates/haml/partials/sidebar.haml +68 -0
- data/assets/templates/haml/partials/symbols/class.haml +1 -0
- data/assets/templates/haml/partials/symbols/demo.haml +23 -0
- data/assets/templates/haml/partials/symbols/factory.haml +38 -0
- data/assets/templates/haml/partials/symbols/function.haml +54 -0
- data/assets/templates/haml/partials/symbols/mixin.haml +31 -0
- data/assets/templates/haml/partials/symbols/variable.haml +22 -0
- data/assets/templates/haml/pattern.haml +54 -0
- data/assets/templates/slim/demo.slim +24 -0
- data/assets/templates/slim/layouts/demo.slim +5 -0
- data/assets/templates/slim/layouts/pattern.slim +48 -0
- data/assets/templates/slim/partials/sidebar.slim +112 -0
- data/assets/templates/slim/partials/symbols/class.slim +1 -0
- data/assets/templates/slim/partials/symbols/demo.slim +30 -0
- data/assets/templates/slim/partials/symbols/factory.slim +57 -0
- data/assets/templates/slim/partials/symbols/function.slim +81 -0
- data/assets/templates/slim/partials/symbols/mixin.slim +45 -0
- data/assets/templates/slim/partials/symbols/variable.slim +35 -0
- data/assets/templates/slim/pattern.slim +63 -0
- data/docks_config.rb +32 -0
- data/docks_theme_api.gemspec +37 -0
- data/gulpfile.js +88 -0
- data/karma.conf.js +6 -0
- data/lib/docks_theme_api/components/base_component.rb +99 -0
- data/lib/docks_theme_api/components/code_block_component.rb +10 -0
- data/lib/docks_theme_api/components/popover_component.rb +15 -0
- data/lib/docks_theme_api/components/table_component.rb +34 -0
- data/lib/docks_theme_api/components/tablist_component.rb +11 -0
- data/lib/docks_theme_api/components.rb +21 -0
- data/lib/docks_theme_api/helpers/ui_helper.rb +69 -0
- data/lib/docks_theme_api/theme.rb +21 -0
- data/lib/docks_theme_api.rb +1 -0
- data/package.json +60 -0
- data/source/behaviors/filterable/filterable.coffee +353 -0
- data/source/behaviors/filterable/filterable.js +0 -0
- data/source/behaviors/filterable/filterable.scss +34 -0
- data/source/behaviors/filterable/package.json +3 -0
- data/source/behaviors/index.js +0 -0
- data/source/components/avatar/avatar.erb +20 -0
- data/source/components/avatar/avatar.js +142 -0
- data/source/components/avatar/avatar.scss +200 -0
- data/source/components/avatar/avatar_container.erb +13 -0
- data/source/components/avatar/package.json +3 -0
- data/source/components/avatar/spec/avatar_spec.js +81 -0
- data/source/components/badge/badge.scss +158 -0
- data/source/components/button/button.scss +213 -0
- data/source/components/card/card.scss +32 -0
- data/source/components/code_block/code-block.scss +353 -0
- data/source/components/code_block/code_block.erb +95 -0
- data/source/components/code_block/code_block.js +444 -0
- data/source/components/code_block/package.json +3 -0
- data/source/components/code_block/spec/code_block_spec.js +10 -0
- data/source/components/demo/demo.js +244 -0
- data/source/components/demo/demo.scss +90 -0
- data/source/components/demo/package.json +3 -0
- data/source/components/exploded/exploded.erb +25 -0
- data/source/components/exploded/exploded.js +694 -0
- data/source/components/exploded/exploded.scss +166 -0
- data/source/components/exploded/package.json +3 -0
- data/source/components/field/field.js +24 -0
- data/source/components/field/field.scss +101 -0
- data/source/components/field/package.json +3 -0
- data/source/components/header/header.scss +33 -0
- data/source/components/iframe/iframe.erb +12 -0
- data/source/components/iframe/iframe.js +381 -0
- data/source/components/iframe/package.json +3 -0
- data/source/components/index.js +37 -0
- data/source/components/inline_group/inline-group.scss +14 -0
- data/source/components/internal_link/internal_link.js +49 -0
- data/source/components/internal_link/package.json +3 -0
- data/source/components/list/list.scss +230 -0
- data/source/components/modal/modal.coffee +84 -0
- data/source/components/modal/modal.erb +19 -0
- data/source/components/modal/modal.js +0 -0
- data/source/components/modal/modal.scss +57 -0
- data/source/components/modal/package.json +3 -0
- data/source/components/notice/notice.scss +48 -0
- data/source/components/popover/package.json +3 -0
- data/source/components/popover/popover.coffee +562 -0
- data/source/components/popover/popover.erb +21 -0
- data/source/components/popover/popover.js +0 -0
- data/source/components/popover/popover.scss +139 -0
- data/source/components/range/range.scss +78 -0
- data/source/components/resizable/package.json +3 -0
- data/source/components/resizable/resizable.erb +30 -0
- data/source/components/resizable/resizable.js +250 -0
- data/source/components/resizable/resizable.scss +245 -0
- data/source/components/resizable/size_buttons.js +249 -0
- data/source/components/scroll_container/package.json +3 -0
- data/source/components/scroll_container/scroll-container.scss +4 -0
- data/source/components/scroll_container/scroll_container.js +24 -0
- data/source/components/section/section.scss +99 -0
- data/source/components/select/package.json +3 -0
- data/source/components/select/select.erb +21 -0
- data/source/components/select/select.js +35 -0
- data/source/components/select/select.scss +163 -0
- data/source/components/table/package.json +3 -0
- data/source/components/table/table.erb +16 -0
- data/source/components/table/table.js +351 -0
- data/source/components/table/table.scss +236 -0
- data/source/components/tablist/package.json +3 -0
- data/source/components/tablist/tablist.erb +13 -0
- data/source/components/tablist/tablist.js +246 -0
- data/source/components/tablist/tablist.scss +191 -0
- data/source/components/tablist/tablist_panel.erb +14 -0
- data/source/components/tablist/tablist_tab.erb +20 -0
- data/source/components/toggle/package.json +3 -0
- data/source/components/toggle/toggle.erb +11 -0
- data/source/components/toggle/toggle.js +211 -0
- data/source/components/toggle/toggle_container.erb +30 -0
- data/source/components/vertical_spacer/vertical-spacer.scss +3 -0
- data/source/components/vertical_stack/vertical-stack.scss +19 -0
- data/source/components/xray/package.json +3 -0
- data/source/components/xray/xray.erb +50 -0
- data/source/components/xray/xray.js +123 -0
- data/source/components/xray/xray.scss +79 -0
- data/source/foundation/app/app.js +15 -0
- data/source/foundation/app/package.json +3 -0
- data/source/pattern-library-demo.scss +13 -0
- data/source/pattern-library.scss +13 -0
- data/source/pattern_library.js +8 -0
- data/source/pattern_library_demo.js +8 -0
- data/source/structures/index.js +11 -0
- data/source/structures/sidebar/package.json +3 -0
- data/source/structures/sidebar/sidebar.js +69 -0
- data/source/structures/sidebar/sidebar.scss +79 -0
- data/source/utilities/builder/builder.js +138 -0
- data/source/utilities/builder/package.json +3 -0
- data/source/utilities/client/client.js +7 -0
- data/source/utilities/client/package.json +3 -0
- data/source/utilities/colors/colors.scss +112 -0
- data/source/utilities/defaults/defaults.scss +38 -0
- data/source/utilities/dom_cache/dom_cache.js +24 -0
- data/source/utilities/dom_cache/package.json +3 -0
- data/source/utilities/events/events.js +25 -0
- data/source/utilities/events/package.json +3 -0
- data/source/utilities/font_sizes/font-sizes.scss +85 -0
- data/source/utilities/foundation/a11y.scss +10 -0
- data/source/utilities/foundation/base.scss +29 -0
- data/source/utilities/foundation/icon.scss +114 -0
- data/source/utilities/foundation/layout.scss +67 -0
- data/source/utilities/foundation/page.scss +39 -0
- data/source/utilities/foundation/type.scss +208 -0
- data/source/utilities/functions/functions.scss +127 -0
- data/source/utilities/keycodes/keycodes.js +23 -0
- data/source/utilities/keycodes/package.json +3 -0
- data/source/utilities/markup/markup.js +90 -0
- data/source/utilities/markup/package.json +3 -0
- data/source/utilities/media/media.scss +172 -0
- data/source/utilities/mixins/mixins.scss +89 -0
- data/source/utilities/naming_convention/naming_convention.js +3 -0
- data/source/utilities/naming_convention/package.json +3 -0
- data/source/utilities/numbers/numbers.js +14 -0
- data/source/utilities/numbers/package.json +3 -0
- data/source/utilities/painting/package.json +3 -0
- data/source/utilities/painting/painting.js +7 -0
- data/source/utilities/pattern/package.json +3 -0
- data/source/utilities/pattern/pattern.js +50 -0
- data/source/utilities/query_string/package.json +3 -0
- data/source/utilities/query_string/query_string.js +24 -0
- data/source/utilities/template/package.json +3 -0
- data/source/utilities/template/template.js +10 -0
- data/source/utilities/text_range/package.json +3 -0
- data/source/utilities/text_range/text_range.js +30 -0
- data/source/utilities/ui_events/package.json +3 -0
- data/source/utilities/ui_events/ui_events.js +85 -0
- data/source/utilities/variables/variables.scss +18 -0
- data/source/utilities/z_indexes/z-indexes.scss +88 -0
- data/source/vendor/array_includes.js +28 -0
- data/source/vendor/highlight.js +1142 -0
- data/source/vendor/index.js +1 -0
- data/source/vendor/matrix.js +399 -0
- data/source/vendor/query_string.js +66 -0
- data/spec/assets/.eslintrc +9 -0
- data/spec/assets/spec_fixture.js +33 -0
- data/spec/assets/spec_helper.js +19 -0
- data/spec/lib/components/base_component_spec.rb +156 -0
- data/spec/lib/components_spec.rb +30 -0
- data/spec/lib/helpers/ui_helper_spec.rb +62 -0
- data/spec/lib/theme_spec.rb +25 -0
- data/spec/spec_helper.rb +15 -0
- data/tasks/gulp/.eslintrc +6 -0
- data/tasks/gulp/browser_sync.js +8 -0
- data/tasks/gulp/code_quality/scripts.js +10 -0
- data/tasks/gulp/config/index.js +116 -0
- data/tasks/gulp/minify/scripts.js +13 -0
- data/tasks/gulp/minify/styles.js +13 -0
- data/tasks/gulp/pattern_library/index.js +5 -0
- data/tasks/gulp/pattern_library/scripts.js +10 -0
- data/tasks/gulp/pattern_library/styles.js +10 -0
- data/tasks/gulp/scripts.js +8 -0
- data/tasks/gulp/spec/scripts.js +11 -0
- data/tasks/gulp/styles.js +17 -0
- data/tasks/gulp/utilities/babel/relative_require.js +22 -0
- data/tasks/gulp/utilities/babel/spec_helper.js +20 -0
- data/tasks/gulp/utilities/browserify_bundler.js +22 -0
- data/tasks/gulp/utilities/handle_errors.js +13 -0
- data/tasks/gulp/watch.js +9 -0
- data/tasks/rake/rspec.rake +7 -0
- data/tasks/rake/rubocop.rake +8 -0
- data/tasks/rake/templates.rake +50 -0
- 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,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>
|