@nycds/button 0.4.0

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.
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@nycds/button",
3
+ "version": "0.4.0",
4
+ "type": "module",
5
+ "main": "src/js/index.js",
6
+ "style": "dist/button.css",
7
+ "dependencies": {
8
+ "@nycds/config": "^0.2.0"
9
+ },
10
+ "scripts": {
11
+ "clean": "rimraf ./dist",
12
+ "build:css:default": "postcss src/css/index.css -o dist/button.css",
13
+ "build:css:aem": "postcss src/css/aem/index.css -o dist/button-aem.css",
14
+ "minify:css": "postcss dist/*.css --use cssnano --ext .min.css --dir dist --no-map"
15
+ }
16
+ }
@@ -0,0 +1 @@
1
+ export { default } from '@nycds/config'
@@ -0,0 +1,86 @@
1
+ /* Generated from README.org - do not edit directly */
2
+ @import '../mixins.css';
3
+
4
+ .nyc-button,
5
+ .nyc-button:link,
6
+ .nyc-button:visited,
7
+ .cmp-button,
8
+ .cmp-button:link,
9
+ .cmp-button:visited,
10
+ .cmp-form-button {
11
+ @mixin button;
12
+ }
13
+
14
+ .nyc-button[data-variant='primary'],
15
+ /* .cmp-button--primary.cmp-button, DEPRECATED */
16
+ .cmp-button--primary .cmp-button,
17
+ .cmp-form-button[type='SUBMIT'] {
18
+ @mixin button-primary;
19
+ }
20
+
21
+ .nyc-button[data-variant='secondary'],
22
+ /* .cmp-button--secondary.cmp-button, DEPRECATED */
23
+ .cmp-button--secondary .cmp-button,
24
+ .cmp-form-button[type='RESET'] {
25
+ @mixin button-secondary;
26
+ }
27
+
28
+ .cmp-button--primary .cmp-button,
29
+ .cmp-button--secondary .cmp-button {
30
+ @mixin button-full-width;
31
+ }
32
+
33
+ /* DEPRECATED */
34
+ /* .cmp-button--secondary--black.cmp-button, */
35
+ /* .cmp-button--secondary--black .cmp-button { */
36
+ /* @mixin button-secondary--black; */
37
+ /* } */
38
+
39
+ .nyc-button[data-variant='filter'],
40
+ .cmp-button--filter {
41
+ @mixin button-filter;
42
+ }
43
+
44
+ .nyc-button[data-variant='icon'],
45
+ /* .cmp-button--icon.cmp-button, DEPRECATED */
46
+ .cmp-button--icon .cmp-button {
47
+ @mixin button-icon;
48
+ }
49
+
50
+ .cmp-button--sm.cmp-button,
51
+ .cmp-button--sm .cmp-button {
52
+ @mixin button-sm;
53
+ }
54
+
55
+ .cmp-button.format--link {
56
+ @mixin button-text-link;
57
+ }
58
+
59
+ .flex-col:has(.button) {
60
+ & .button {
61
+ display: flex;
62
+ }
63
+
64
+ & .cmp-button {
65
+ flex: 1;
66
+ }
67
+ }
68
+
69
+ .cmp-button--full-width.cmp-button,
70
+ .cmp-button--full-width .cmp-button {
71
+ @mixin button-full-width;
72
+ }
73
+
74
+ .nyc-back-to-top {
75
+ display: contents;
76
+ }
77
+
78
+ nyc-back-to-top {
79
+ @mixin layout-back-to-top;
80
+ }
81
+
82
+ .button--back-to-top {
83
+ .cmp-button {
84
+ @mixin button-back-to-top;
85
+ }
86
+ }
@@ -0,0 +1,40 @@
1
+ /* Generated from README.org - do not edit directly */
2
+ @import './mixins.css';
3
+
4
+ .nyc-button,
5
+ .nyc-button:link,
6
+ .nyc-button:visited {
7
+ @mixin button;
8
+ }
9
+
10
+ .nyc-button[data-variant='primary'] {
11
+ @mixin button-primary;
12
+ }
13
+
14
+ .nyc-button[data-variant='secondary'] {
15
+ @mixin button-secondary;
16
+ }
17
+
18
+ .nyc-button[data-variant='secondary--black'] {
19
+ @mixin button-secondary--black;
20
+ }
21
+
22
+ .nyc-button[data-variant='icon'] {
23
+ @mixin button-icon;
24
+ }
25
+
26
+ .nyc-button[data-size='sm'] {
27
+ @mixin button-sm;
28
+ }
29
+
30
+ .nyc-button[data-variant='filter'] {
31
+ @mixin button-filter;
32
+ }
33
+
34
+ nyc-back-to-top {
35
+ @mixin layout-back-to-top;
36
+ }
37
+
38
+ button[data-variant='back-to-top'] {
39
+ @mixin button-back-to-top;
40
+ }
@@ -0,0 +1,380 @@
1
+ @define-mixin button {
2
+ --_background: var(--nyc-cmp-button-background, #103FEF);
3
+ --_background-hover: var(--nyc-cmp-button-background-hover, #050560);
4
+ --_border-color: var(--nyc-cmp-button-border-color, #103FEF);
5
+ --_border-color-hover: var(--nyc-cmp-button-border-color-hover, #050560);
6
+ --_border-width: var(--nyc-cmp-button-border-width, 2px);
7
+ --_border-radius: var(--nyc-cmp-button-border-radius, 0);
8
+ --_color: var(--nyc-cmp-button-color, #FFFFFF);
9
+ --_color-hover: var(--nyc-cmp-button-color-hover, #FFFFFF);
10
+ --_font-weight: var(--nyc-cmp-button-font-weight, 600);
11
+ --_font-size: var(--nyc-cmp-button-font-size, 1rem);
12
+ --_shadow: var(--nyc-cmp-button-shadow, none);
13
+ --_padding-block: var(--nyc-cmp-button-padding-block, 0.75rem);
14
+ --_padding-inline: var(--nyc-cmp-button-padding-inline, 0.75rem);
15
+ --_outline-color: var(--nyc-cmp-button-outline-color, inherit);
16
+ --_gap: var(--nyc-cmp-button-gap, 0.5rem);
17
+ --_size-icon: var(--_font-size);
18
+
19
+ display: inline-flex;
20
+ gap: calc(var(--_gap) * 0.8);
21
+ align-items: center;
22
+ justify-content: center;
23
+ padding: var(--_padding-block) var(--_padding-inline);
24
+ font-size: var(--_font-size);
25
+ font-weight: var(--_font-weight);
26
+ line-height: 1.2;
27
+ color: var(--_color);
28
+ text-align: center;
29
+ text-decoration: none;
30
+ appearance: none;
31
+ cursor: pointer;
32
+ background-color: var(--_background);
33
+ border-color: var(--_border-color);
34
+ border-style: solid;
35
+ border-width: var(--_border-width);
36
+ border-radius: var(--_border-radius);
37
+ box-shadow: var(--_shadow);
38
+ transition: all 0.3s ease;
39
+
40
+ &:hover {
41
+ color: var(--_color-hover);
42
+ text-decoration: none;
43
+ background: var(--_background-hover);
44
+ border-color: var(--_border-color-hover);
45
+ }
46
+
47
+ &:focus-visible {
48
+ outline: 2px dashed var(--_outline-color);
49
+ outline-offset: 0.25rem;
50
+ border-radius: var(--_border-radius);
51
+ }
52
+
53
+ &[disabled], &.disabled, &:disabled {
54
+ --nyc-cmp-button-color: var(--nyc-sys-color-on-disabled) !important;
55
+ --nyc-cmp-button-background: var(--nyc-sys-color-disabled) !important;
56
+ --nyc-cmp-button-border-color: var(--nyc-sys-color-on-disabled) !important;
57
+ }
58
+
59
+ & * {
60
+ pointer-events: none;
61
+ }
62
+
63
+ &.active {
64
+ color: white;
65
+ background: var(--_color-hover);
66
+ }
67
+
68
+ &.size--xs {
69
+ --_font-size: calc(0.68 * var(--nyc-cmp-button-font-size, 1rem));
70
+ --_padding-block: calc(0.58 * var(--nyc-cmp-button-padding-block, 0.75rem));
71
+ --_padding-inline: calc(0.4 * var(--nyc-cmp-button-padding-inline, 0.75rem));
72
+ }
73
+
74
+ &.size--s {
75
+ --_font-size: calc(0.84 * var(--nyc-cmp-button-font-size, 1rem));
76
+ --_padding-block: calc(0.66 * var(--nyc-cmp-button-padding-block, 0.75rem));
77
+ --_padding-inline: calc(0.54 * var(--nyc-cmp-button-padding-inline, 0.75rem));
78
+ }
79
+
80
+ &.size--nm {
81
+ --_font-size: calc(0.92 * var(--nyc-cmp-button-font-size, 1rem));
82
+ --_padding-block: calc(0.8 * var(--nyc-cmp-button-padding-block, 0.75rem));
83
+ --_padding-inline: calc(0.62 * var(--nyc-cmp-button-padding-inline, 0.75rem));
84
+ }
85
+
86
+ .rim-color--blue,
87
+ &.rim-color--blue {
88
+ --_border-color: var(--nyc-sys-color-primary);
89
+
90
+ &:hover {
91
+ --_border-color-hover: var(--nyc-sys-color-primary);
92
+ }
93
+ }
94
+
95
+ .shape--square,
96
+ &.shape--square {
97
+ border-radius: 0;
98
+ }
99
+
100
+ .shape--rounded,
101
+ &.shape--rounded {
102
+ border-radius: calc(0.15 * var(--_border-radius));
103
+ }
104
+
105
+ .shape--circle,
106
+ &.shape--circle {
107
+ border-radius: 50%;
108
+ }
109
+
110
+ .shape--square,
111
+ &.shape--square,
112
+ .shape--circle,
113
+ &.shape--circle {
114
+ width: calc(2 * var(--_padding-block) + var(--_font-size) * 1.15);
115
+ height: calc(2 * var(--_padding-block) + var(--_font-size) * 1.15);
116
+ }
117
+
118
+ &.has-count {
119
+ --nyc-cmp-button-background: var(--nyc-ref-color-blue-90) !important;
120
+ --nyc-cmp-button-color: var(--nyc-sys-color-primary) !important;
121
+ --nyc-cmp-button-border-color: var(--nyc-sys-color-primary) !important;
122
+ }
123
+
124
+ .icon {
125
+ position: relative;
126
+ z-index: 20;
127
+ display: flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ width: var(--_size-icon);
131
+ height: var(--_size-icon);
132
+ font-size: calc(0.75 * var(--_size-icon));
133
+ font-style: normal;
134
+ font-weight: 300;
135
+ text-align: center;
136
+ pointer-events: none;
137
+
138
+ svg {
139
+ fill: var(--nyc-cmp-button-color);
140
+ }
141
+
142
+ &.i--count {
143
+ width: 22px;
144
+ height: 22px;
145
+ font-weight: 600;
146
+ line-height: 0;
147
+ color: white;
148
+ background-color: var(--nyc-cmp-button-color);
149
+ }
150
+ }
151
+ }
152
+
153
+ @define-mixin button-primary {
154
+ --nyc-cmp-button-shadow: none;
155
+ --nyc-cmp-button-background: var(--nyc-sys-color-primary);
156
+ --nyc-cmp-button-background-hover: var(--nyc-sys-color-primary-hover);
157
+
158
+ --nyc-cmp-button-color: var(--nyc-sys-color-on-primary);
159
+ --nyc-cmp-button-color-hover: var(--nyc-cmp-button-color);
160
+
161
+ --nyc-cmp-button-border-color: var(--nyc-sys-color-primary);
162
+ --nyc-cmp-button-border-color-hover: var(--nyc-sys-color-primary-hover);
163
+ }
164
+
165
+ @define-mixin button-secondary {
166
+ --nyc-cmp-button-shadow: none;
167
+ --nyc-cmp-button-background: var(--nyc-sys-color-secondary);
168
+ --nyc-cmp-button-background-hover: var(--nyc-sys-color-secondary);
169
+
170
+ --nyc-cmp-button-color: var(--nyc-sys-color-on-secondary);
171
+ --nyc-cmp-button-color-hover: var(--nyc-sys-color-on-secondary-hover);
172
+
173
+ --nyc-cmp-button-border-color: var(--nyc-sys-color-border-base);
174
+ --nyc-cmp-button-border-color-hover: var(--nyc-sys-color-border-base);
175
+ }
176
+
177
+ @define-mixin button-secondary--black {
178
+ --nyc-cmp-button-color: var(--nyc-sys-color-text-base);
179
+ --nyc-cmp-button-color-hover: var(--nyc-sys-color-on-secondary-hover);
180
+
181
+ --nyc-cmp-button-border-color: var(--nyc-sys-color-text-base);
182
+ --nyc-cmp-button-border-color-hover: var(--nyc-sys-color-border-base);
183
+ }
184
+
185
+ @define-mixin button-filter {
186
+ --nyc-cmp-button-border-radius: 0.25rem;
187
+ --nyc-cmp-button-font-size: var(--nyc-sys-size-text-sm);
188
+ --nyc-cmp-button-padding-inline: var(--nyc-sys-space-sm);
189
+ --nyc-cmp-button-padding-block: var(--nyc-sys-space-sm);
190
+ --nyc-cmp-button-border-color: var(--nyc-sys-color-border-medium);
191
+ --nyc-cmp-button-background: var(--nyc-sys-color-surface-base);
192
+ --nyc-cmp-button-background-hover: var(--nyc-sys-color-surface-low);
193
+ --nyc-cmp-button-color: var(--nyc-sys-color-on-surface-base);
194
+ --nyc-cmp-button-color-hover: var(--nyc-sys-color-on-surface-base);
195
+ --nyc-cmp-button-font-weight: 700;
196
+
197
+ line-height: 1.5;
198
+
199
+ &[data-selected] {
200
+ --nyc-cmp-button-background: var(--nyc-sys-color-surface-input-highlight);
201
+ --nyc-cmp-button-border-color: var(--nyc-sys-color-border-selected);
202
+ --nyc-cmp-button-color: var(--nyc-sys-color-primary);
203
+ }
204
+
205
+ @media (width >= 768px) {
206
+ --nyc-cmp-button-border-radius: 0.5rem;
207
+ --nyc-cmp-button-font-size: var(--nyc-sys-size-text-base);
208
+ --nyc-cmp-button-padding-inline: var(--nyc-sys-space-base);
209
+ }
210
+ }
211
+
212
+ @define-mixin button-icon {
213
+ --nyc-cmp-button-shadow: none;
214
+ --nyc-cmp-button-background: var(--nyc-sys-color-primary);
215
+ --nyc-cmp-button-background-hover: var(--nyc-sys-color-primary-hover);
216
+ --nyc-cmp-button-padding-inline: var(--nyc-cmp-button-padding-block);
217
+
218
+ --nyc-cmp-button-color: var(--nyc-sys-color-on-primary);
219
+ --nyc-cmp-button-color-hover: var(--nyc-sys-color-on-primary);
220
+
221
+ --nyc-cmp-button-border-color: var(--nyc-sys-color-border-base);
222
+ --nyc-cmp-button-border-color-hover: var(--nyc-sys-color-border-base);
223
+
224
+ &:hover {
225
+ box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.20); /* TODO: add token */
226
+ }
227
+ }
228
+
229
+ @define-mixin button-sm {
230
+ --nyc-cmp-button-border-radius: var(--nyc-sys-radius-lg);
231
+ --nyc-cmp-button-padding-block: var(--nyc-sys-space-2xs);
232
+ --nyc-cmp-button-padding-inline: var(--nyc-sys-space-2xs);
233
+ --nyc-cmp-button-font-size: var(--nyc-sys-size-body-base);
234
+ --nyc-cmp-button-font-weight: 600;
235
+ }
236
+
237
+ @define-mixin button-text-link {
238
+ --nyc-cmp-button-color: var(--nyc-sys-color-primary);
239
+ --nyc-cmp-button-background: transparent;
240
+ --_padding-inline: 5px;
241
+
242
+ text-decoration: underline;
243
+ border: none;
244
+
245
+ &:hover {
246
+ --nyc-cmp-button-color: var(--nyc-sys-color-link-hover);
247
+ }
248
+
249
+ &.has-icon {
250
+ --_padding-block: 0.75rem;
251
+ --_padding-inline: 0.75rem;
252
+ }
253
+ }
254
+
255
+ @define-mixin button-full-width {
256
+ @media (width < 768px) {
257
+ &:first-child {
258
+ width: 100%;
259
+ }
260
+ }
261
+ }
262
+
263
+ @define-mixin layout-back-to-top {
264
+ display: block;
265
+ margin: var(--nyc-sys-space-3xl) var(--nyc-sys-space-fluid-base-5xl) var(--nyc-sys-space-5xl);
266
+ transition: opacity 0.3s ease;
267
+
268
+ @media (width >= 768px) {
269
+ position: sticky;
270
+ bottom: var(--nyc-sys-space-5xl);
271
+ left: var(--nyc-sys-space-fluid-base-5xl);
272
+ z-index: 150;
273
+ display: inline-block;
274
+ margin: unset;
275
+ }
276
+
277
+ &.enabled {
278
+ @media (width >= 768px) {
279
+ visibility: visible;
280
+ pointer-events: all;
281
+ opacity: 1;
282
+ }
283
+ }
284
+
285
+ &:not(.enabled) {
286
+ @media (width >= 768px) {
287
+ visibility: hidden;
288
+ pointer-events: none;
289
+ opacity: 0;
290
+ }
291
+ }
292
+ }
293
+
294
+ @define-mixin button-back-to-top {
295
+ @mixin button-secondary;
296
+
297
+ --nyc-cmp-button-border-color: var(--nyc-cmp-button-color);
298
+ --nyc-cmp-button-border-width: 2px;
299
+ --nyc-cmp-button-gap: 0;
300
+ --nyc-cmp-button-font-size: var(--nyc-sys-size-body-base);
301
+ --nyc-cmp-button-font-weight: 600;
302
+ --nyc-cmp-button-padding-block: var(--nyc-sys-space-sm);
303
+ --nyc-cmp-button-padding-inline: var(--nyc-sys-space-sm);
304
+
305
+ width: 100%;
306
+
307
+ @media (width >= 768px) {
308
+ --nyc-cmp-button-border-width: 3px;
309
+ --nyc-cmp-button-font-size: var(--nyc-sys-space-base);
310
+ --nyc-cmp-button-padding-block: var(--nyc-sys-space-base);
311
+ --nyc-cmp-button-padding-inline: var(--nyc-sys-space-base);
312
+
313
+ width: unset;
314
+ }
315
+
316
+ /* Icon */
317
+ &::before {
318
+ --icon-size: calc(1em - (var(--nyc-cmp-button-border-width) * 2));
319
+ --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath stroke='currentColor' fill='%23000' d='M205.66 117.66a8 8 0 0 1-11.32 0L136 59.31V216a8 8 0 0 1-16 0V59.31l-58.34 58.35a8 8 0 0 1-11.32-11.32l72-72a8 8 0 0 1 11.32 0l72 72a8 8 0 0 1 0 11.32'/%3E%3C/svg%3E");
320
+
321
+ display: inline-block;
322
+ width: var(--icon-size);
323
+ height: var(--icon-size);
324
+ font-size: var(--nyc-sys-space-lg);
325
+ content: '';
326
+ background-color: currentcolor;
327
+ -webkit-mask-image: var(--svg);
328
+ mask-image: var(--svg);
329
+ -webkit-mask-repeat: no-repeat;
330
+ mask-repeat: no-repeat;
331
+ -webkit-mask-size: 100% 100%;
332
+ mask-size: 100% 100%;
333
+
334
+ @media (width >= 768px) {
335
+ font-size: var(--nyc-sys-space-2xl);
336
+ }
337
+ }
338
+
339
+ span {
340
+ --space-btwn: var(--nyc-sys-space-base);
341
+
342
+ padding-left: var(--space-btwn);
343
+ line-height: 1.1;
344
+ text-align: left;
345
+ opacity: 1;
346
+ transition:
347
+ padding 0.3s ease,
348
+ max-width 0.3s ease,
349
+ opacity 0.3s 0.3s ease;
350
+
351
+ @media (width >= 768px) {
352
+ max-width: calc(3.5em + var(--space-btwn));
353
+ max-height: 2.2em;
354
+ padding-left: var(--space-btwn);
355
+ overflow: hidden;
356
+ }
357
+ }
358
+
359
+ &:hover {
360
+ color: var(--nyc-sys-color-primary);
361
+ border-color: var(--nyc-sys-color-primary);
362
+ }
363
+
364
+ /* Desktop unhovered/closed state */
365
+ &:not(:hover) {
366
+ span {
367
+ transition:
368
+ padding 0.3s 0.2s ease,
369
+ max-width 0.3s 0.2s ease,
370
+ opacity 0.3s ease;
371
+
372
+ @media (width >= 768px) {
373
+ visibility: hidden;
374
+ max-width: 0;
375
+ padding-left: unset;
376
+ opacity: 0;
377
+ }
378
+ }
379
+ }
380
+ }
@@ -0,0 +1,90 @@
1
+ // See apps/pocs/src/js/components/NYCBackToTop.js for example implementation
2
+ export default class BackToTop extends HTMLElement {
3
+ static define(name = 'nyc-back-to-top') {
4
+ customElements.define(name, this)
5
+ }
6
+
7
+ constructor() {
8
+ super()
9
+ this.isLongPage = false
10
+ this.isScrolledEnough = false
11
+ this.isScrollTick = false
12
+ this.button = this.querySelector('#back-to-top')
13
+
14
+ // For observing resizing of dynamic content
15
+ this.dynamicContainer = document.querySelector('.backtotop-observe')
16
+ this.resizeObserver = null
17
+ this.queuedHeightCheck = null
18
+ }
19
+
20
+ connectedCallback() {
21
+ this.#queueCheckHeights()
22
+ window.addEventListener('scroll', this.#onScroll.bind(this))
23
+ this.button.addEventListener('click', this.#onClick.bind(this))
24
+
25
+ // If dynamic content exists, recheck height when dynamic container size changes
26
+ if (this.dynamicContainer) {
27
+ this.resizeObserver = new ResizeObserver(this.#queueCheckHeights.bind(this))
28
+ this.resizeObserver.observe(this.dynamicContainer)
29
+ } else {
30
+ window.addEventListener('resize', this.#queueCheckHeights.bind(this))
31
+ }
32
+ }
33
+
34
+ disconnectedCallback() {
35
+ window.removeEventListener('scroll', this.#onScroll.bind(this))
36
+ this.button.removeEventListener('click', this.#onClick.bind(this))
37
+
38
+ if (this.dynamicContainer) {
39
+ this.resizeObserver.disconnect()
40
+ } else {
41
+ window.removeEventListener('resize', this.#queueCheckHeights.bind(this))
42
+ }
43
+ }
44
+
45
+ #checkHeights() {
46
+ const pageHeight = document.documentElement.scrollHeight
47
+ const windowHeight = window.innerHeight
48
+
49
+ this.isLongPage = pageHeight > 4 * windowHeight
50
+ this.#updateShowHide()
51
+ }
52
+
53
+ #checkScroll() {
54
+ const scrollY = window.scrollY
55
+ const windowHeight = window.innerHeight
56
+
57
+ this.isScrolledEnough = scrollY > windowHeight
58
+ this.#updateShowHide()
59
+ }
60
+
61
+ #onClick(e) {
62
+ // Reset stored Focus, then scroll to top (for tab accessibility)
63
+ e.preventDefault()
64
+ document.querySelector('a').focus()
65
+ document.querySelector('a').blur()
66
+ window.scrollTo({ top: 0, behavior: 'smooth' })
67
+ }
68
+
69
+ #onScroll() {
70
+ if (this.isScrollTick) return
71
+ this.isScrollTick = true
72
+ setTimeout(() => {
73
+ this.#checkScroll()
74
+ this.isScrollTick = false
75
+ }, 500)
76
+ }
77
+
78
+ #queueCheckHeights() {
79
+ this.queuedHeightCheck && clearTimeout(this.queuedHeightCheck)
80
+ this.queuedHeightCheck = setTimeout(this.#checkHeights.bind(this), 1000)
81
+ }
82
+
83
+ #updateShowHide() {
84
+ if (this.isLongPage && this.isScrolledEnough) {
85
+ this.classList.add('enabled')
86
+ } else {
87
+ this.classList.remove('enabled')
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,90 @@
1
+ // See apps/pocs/src/js/components/NYCBackToTop.js for example implementation
2
+ export class NYCBackToTop extends HTMLElement {
3
+ static define(name = 'nyc-back-to-top') {
4
+ customElements.define(name, this)
5
+ }
6
+
7
+ constructor() {
8
+ super()
9
+ this.isLongPage = false
10
+ this.isScrolledEnough = false
11
+ this.isScrollTick = false
12
+ this.button = this.querySelector('#back-to-top')
13
+
14
+ // For observing resizing of dynamic content
15
+ this.dynamicContainer = document.querySelector('.backtotop-observe')
16
+ this.resizeObserver = null
17
+ this.queuedHeightCheck = null
18
+ }
19
+
20
+ connectedCallback() {
21
+ this.#queueCheckHeights()
22
+ window.addEventListener('scroll', this.#onScroll.bind(this))
23
+ this.button.addEventListener('click', this.#onClick.bind(this))
24
+
25
+ // If dynamic content exists, recheck height when dynamic container size changes
26
+ if (this.dynamicContainer) {
27
+ this.resizeObserver = new ResizeObserver(this.#queueCheckHeights.bind(this))
28
+ this.resizeObserver.observe(this.dynamicContainer)
29
+ } else {
30
+ window.addEventListener('resize', this.#queueCheckHeights.bind(this))
31
+ }
32
+ }
33
+
34
+ disconnectedCallback() {
35
+ window.removeEventListener('scroll', this.#onScroll.bind(this))
36
+ this.button.removeEventListener('click', this.#onClick.bind(this))
37
+
38
+ if (this.dynamicContainer) {
39
+ this.resizeObserver.disconnect()
40
+ } else {
41
+ window.removeEventListener('resize', this.#queueCheckHeights.bind(this))
42
+ }
43
+ }
44
+
45
+ #checkHeights() {
46
+ const pageHeight = document.documentElement.scrollHeight
47
+ const windowHeight = window.innerHeight
48
+
49
+ this.isLongPage = pageHeight > 4 * windowHeight
50
+ this.#updateShowHide()
51
+ }
52
+
53
+ #checkScroll() {
54
+ const scrollY = window.scrollY
55
+ const windowHeight = window.innerHeight
56
+
57
+ this.isScrolledEnough = scrollY > windowHeight
58
+ this.#updateShowHide()
59
+ }
60
+
61
+ #onClick(e) {
62
+ // Reset stored Focus, then scroll to top (for tab accessibility)
63
+ e.preventDefault()
64
+ document.querySelector('a').focus()
65
+ document.querySelector('a').blur()
66
+ window.scrollTo({ top: 0, behavior: 'smooth' })
67
+ }
68
+
69
+ #onScroll() {
70
+ if (this.isScrollTick) return
71
+ this.isScrollTick = true
72
+ setTimeout(() => {
73
+ this.#checkScroll()
74
+ this.isScrollTick = false
75
+ }, 500)
76
+ }
77
+
78
+ #queueCheckHeights() {
79
+ this.queuedHeightCheck && clearTimeout(this.queuedHeightCheck)
80
+ this.queuedHeightCheck = setTimeout(this.#checkHeights.bind(this), 1000)
81
+ }
82
+
83
+ #updateShowHide() {
84
+ if (this.isLongPage && this.isScrolledEnough) {
85
+ this.classList.add('enabled')
86
+ } else {
87
+ this.classList.remove('enabled')
88
+ }
89
+ }
90
+ }
@@ -0,0 +1 @@
1
+ export { NYCBackToTop as BackToTop } from '../NYCBackToTop.js'
@@ -0,0 +1 @@
1
+ export { NYCBackToTop } from './NYCBackToTop.js'