-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Creating Skip Navigation Links #29
base: main
Are you sure you want to change the base?
Changes from 7 commits
a991a7c
79beb10
6292998
3749d60
8958c57
51de6f0
34b6a1b
dd60aa5
ec03f37
2e38251
f60c33a
7954a15
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
--- | ||
import Button from '@components/Button/Button.astro'; | ||
|
||
interface Props { | ||
label?: string; | ||
class?: string; | ||
target?: string; | ||
} | ||
|
||
const { label = 'Skip to main content', target, class: propsClass, ...rest } = Astro.props; | ||
|
||
const classes = ['c-skip-link', propsClass]; | ||
--- | ||
|
||
<c-skip-link class:list={classes} {...rest} data-no-swup> | ||
<Button href="#main">{label}</Button> | ||
</c-skip-link> | ||
|
||
<style is:global> | ||
@import 'SkipLink.scss'; | ||
</style> | ||
|
||
<script src="./SkipLink.ts"></script> |
Jerek0 marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// ========================================================================== | ||
// Components / Skip Link Button | ||
// ========================================================================== | ||
|
||
.c-skip-link { | ||
position: fixed; | ||
top: theme-spacing('fluid-sm'); | ||
left: 50%; | ||
opacity: 0; | ||
transform: translate3d(-50%, -100%, 0); | ||
z-index: theme-z('skip'); | ||
white-space: nowrap; | ||
pointer-events: none; | ||
transition: | ||
opacity theme-speed() theme-ease(), | ||
transform theme-speed() theme-ease(); | ||
|
||
@media (prefers-reduced-motion) { | ||
transition: none; | ||
} | ||
|
||
&:focus-within { | ||
pointer-events: all; | ||
opacity: 1; | ||
transform: translate3d(-50%, 0, 0); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,71 @@ | ||||||||||||||
export default class SkipLink extends HTMLElement { | ||||||||||||||
private $link: HTMLLinkElement | null; | ||||||||||||||
|
||||||||||||||
constructor() { | ||||||||||||||
super(); | ||||||||||||||
|
||||||||||||||
// Binding | ||||||||||||||
this.onClick = this.onClick.bind(this); | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rebinding is unnecessary if you declare your method as an arrow function: - onClick(e: Event) {
+ onClick: (e: Event) => { There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To my understanding that syntax is not supported inside of a This would work if we moved the function inside the constructor but I'm not found of the idea There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not fond of moving it to the constructor either. It does work within classes as a class field. It links There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh so you meant - onClick: (e: Event) => {
+ onClick = (e: Event) => { Indeed that works, thanks for the tip! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh 🤦 Yes. |
||||||||||||||
|
||||||||||||||
// UI | ||||||||||||||
this.$link = this.firstElementChild as HTMLLinkElement; | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know how Astro handles custom elements but with native custom elements, you can't expect an element's children to be available during the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Web components defined inside of an Astro component are loaded & executed after the main That being said, we could indeed move that assignation to the |
||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// ============================================================================= | ||||||||||||||
// Lifecycle | ||||||||||||||
// ============================================================================= | ||||||||||||||
connectedCallback() { | ||||||||||||||
this.bindEvents(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
disconnectedCallback() { | ||||||||||||||
this.unbindEvents(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// ============================================================================= | ||||||||||||||
// Events | ||||||||||||||
// ============================================================================= | ||||||||||||||
bindEvents() { | ||||||||||||||
if (this.$link) { | ||||||||||||||
this.$link.addEventListener('click', this.onClick); | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
unbindEvents() { | ||||||||||||||
if (this.$link) { | ||||||||||||||
this.$link.removeEventListener('click', this.onClick); | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// ============================================================================= | ||||||||||||||
// Callbacks | ||||||||||||||
// ============================================================================= | ||||||||||||||
onClick(e: Event) { | ||||||||||||||
e.preventDefault(); | ||||||||||||||
|
||||||||||||||
const $target = document.querySelector( | ||||||||||||||
this.$link?.getAttribute('href') ?? 'main' | ||||||||||||||
) as HTMLElement; | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For consistency with the default value of
Suggested change
|
||||||||||||||
|
||||||||||||||
if (!$target) { | ||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
$target.scrollIntoView({ | ||||||||||||||
behavior: 'smooth', | ||||||||||||||
block: 'start' | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
$target.focus(); | ||||||||||||||
|
||||||||||||||
if (document.activeElement === $target) { | ||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
$target.setAttribute('tabindex', '-1'); | ||||||||||||||
$target.focus(); | ||||||||||||||
|
||||||||||||||
$target.addEventListener('blur', () => $target.removeAttribute('tabindex'), { once: true }); | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
customElements.define('c-skip-link', SkipLink); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A Web site can have more than one skip link that target different locations in a document. We could reasonably assume that the first, and if only, skip link is one that targets a
#main
element.