Last active
April 6, 2022 15:29
-
-
Save jdanyow/73a68fa2f0d9e1f7084bf4b6a294755d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>GistRun</title> | |
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@microsoft/atlas-css/dist/index.css"> | |
<!-- fake design system styles or application styles to illustrate styling the "public interface" of the web component (the component itself as well as "parts" it's exposed for styling) --> | |
<style> | |
.custom-star-rating-1 { | |
color: darkcyan; | |
} | |
.custom-star-rating-2 { | |
color: chocolate; | |
} | |
.custom-star-rating-2::part(star) { | |
color: deeppink; | |
stroke: rebeccapurple; | |
} | |
</style> | |
</head> | |
<body> | |
<!-- application html --> | |
<form class="padding-sm" id="rating-form"> | |
<star-rating class="margin-bottom-sm" name="rating-1" value="4"> | |
<legend slot="legend">How are we doing?</legend> | |
<span slot="label-1">Terrible</span> | |
<span slot="label-2">Poor</span> | |
<span slot="label-3">Fair</span> | |
<span slot="label-4">Good</span> | |
<span slot="label-5">Great</span> | |
</star-rating> | |
<star-rating class="theme-dark padding-sm margin-bottom-sm" name="rating-1" value="4"> | |
<legend slot="legend">How are <strong>we doing</strong>?</legend> | |
<span slot="label-1">Terrible</span> | |
<span slot="label-2">Poor</span> | |
<span slot="label-3">Fair</span> | |
<span slot="label-4">Good</span> | |
<span slot="label-5">Great</span> | |
</star-rating> | |
<star-rating class="margin-bottom-sm custom-star-rating-1" name="rating-2" value="1"> | |
<legend slot="legend">Did this answer tickle your fancy?</legend> | |
<span slot="label-1">Not in the <strong>slightest</strong></span> | |
<span slot="label-2">No</span> | |
<span slot="label-3">Maybe</span> | |
<span slot="label-4">A bit</span> | |
<span slot="label-5">Yes!</span> | |
</star-rating> | |
<star-rating class="margin-block-sm custom-star-rating-2" name="rating-3" value="2"> | |
<legend slot="legend">Did this web component make you <strong>smile</strong>?</legend> | |
<span slot="label-1">Not in the slightest</span> | |
<span slot="label-2">No</span> | |
<span slot="label-3">Maybe</span> | |
<span slot="label-4">A bit</span> | |
<span slot="label-5">Yes!</span> | |
</star-rating> | |
<button type="submit" class="button">Submit</button> | |
<ul class="margin-block-sm"> | |
<li><a href="https://web.dev/more-capable-form-controls/">https://web.dev/more-capable-form-controls/</a></li> | |
<li><a href="https://dev.to/43081j/using-css-shadow-parts-in-web-components-7h5">https://dev.to/43081j/using-css-shadow-parts-in-web-components-7h5</a></li> | |
<li><a href="https://css-tricks.com/styling-web-components/">https://css-tricks.com/styling-web-components/</a></li> | |
<li><a href="https://medium.com/swlh/adopt-a-design-system-inside-your-web-components-with-constructable-stylesheets-dd24649261e">https://medium.com/swlh/adopt-a-design-system-inside-your-web-components-with-constructable-stylesheets-dd24649261e</a></li> | |
</ul> | |
</form> | |
<!-- application code --> | |
<script> | |
const form = document.getElementById('rating-form'); | |
form.addEventListener('submit', event => { | |
event.preventDefault(); | |
// read the form data and convert it to an object. | |
const data = Object.fromEntries(new FormData(form)); | |
// display the data | |
alert(JSON.stringify(data, null, 2)) | |
}); | |
</script> | |
<!-- web component template --> | |
<template id="star-rating-template"> | |
<style type="text/css"> | |
*, ::before, ::after { | |
box-sizing: border-box; | |
} | |
:host { | |
display: block; | |
} | |
input { | |
clip: rect(0 0 0 0); | |
clip-path: inset(50%); | |
height: 1px; | |
overflow: hidden; | |
position: absolute; | |
white-space: nowrap; | |
width: 1px; | |
} | |
svg { | |
fill: none; | |
stroke: currentColor; | |
} | |
label:hover > svg, | |
input:checked + label > svg { | |
fill: currentColor; | |
} | |
/* todo: use `part` and adopt atlas focus style */ | |
input:focus + label { | |
outline: 1px solid blue; | |
} | |
/* checked styles */ | |
[id^="label-"] { | |
display: none; | |
} | |
input[value="1"]:checked ~ #alert #label-1, | |
input[value="2"]:checked ~ #alert #label-2, | |
input[value="3"]:checked ~ #alert #label-3, | |
input[value="4"]:checked ~ #alert #label-4, | |
input[value="5"]:checked ~ #alert #label-5 { | |
display: inline; | |
} | |
/* override checked styles with hover styles */ | |
input:hover ~ #alert [id^="label-"] { | |
display: none !important; | |
} | |
input[value="1"]:hover ~ #alert #label-1, | |
input[value="2"]:hover ~ #alert #label-2, | |
input[value="3"]:hover ~ #alert #label-3, | |
input[value="4"]:hover ~ #alert #label-4, | |
input[value="5"]:hover ~ #alert #label-5 { | |
display: inline !important; | |
} | |
</style> | |
<slot name="legend">Enter rating</slot> | |
<input type="radio" name="rating" value="0" checked hidden /> | |
<input type="radio" name="rating" value="1" id="radio-1" /> | |
<label for="radio-1"><svg part="star" aria-label="5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 51 48"><path d="m25,1 6,17h18l-14,11 5,17-15-10-15,10 5-17-14-11h18z"/></svg></label> | |
<input type="radio" name="rating" value="2" id="radio-2" /> | |
<label for="radio-2"><svg part="star" aria-label="5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 51 48"><path d="m25,1 6,17h18l-14,11 5,17-15-10-15,10 5-17-14-11h18z"/></svg></label> | |
<input type="radio" name="rating" value="3" id="radio-3" /> | |
<label for="radio-3"><svg part="star" aria-label="5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 51 48"><path d="m25,1 6,17h18l-14,11 5,17-15-10-15,10 5-17-14-11h18z"/></svg></label> | |
<input type="radio" name="rating" value="4" id="radio-4" /> | |
<label for="radio-4"><svg part="star" aria-label="5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 51 48"><path d="m25,1 6,17h18l-14,11 5,17-15-10-15,10 5-17-14-11h18z"/></svg></label> | |
<input type="radio" name="rating" value="5" id="radio-5" /> | |
<label for="radio-5"><svg part="star" aria-label="5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 51 48"><path d="m25,1 6,17h18l-14,11 5,17-15-10-15,10 5-17-14-11h18z"/></svg></label> | |
<span id="alert" aria-live="polite"> | |
<span id="label-1"><slot name="label-1"></slot></span> | |
<span id="label-2"><slot name="label-2"></slot></span> | |
<span id="label-3"><slot name="label-3"></slot></span> | |
<span id="label-4"><slot name="label-4"></slot></span> | |
<span id="label-5"><slot name="label-5"></slot></span> | |
</span> | |
</template> | |
<!-- web component javascript --> | |
<script> | |
const template = document.getElementById("star-rating-template"); | |
class StarRatingElement extends HTMLElement { | |
static get observedAttributes() { return ['value']; } | |
constructor() { | |
super(); | |
this.attachShadow({ mode: "open" }); | |
this.shadowRoot.appendChild(template.content.cloneNode(true)); | |
} | |
get value() { return Math.max(0, Math.min(parseInt(this.getAttribute('value') || 0), 5)) }; | |
set value(value) { this.setAttribute('value', value); } | |
connectedCallback() { | |
this.shadowRoot.addEventListener('change', this); | |
this.closest('form')?.addEventListener('formdata', this); | |
} | |
disconnectedCallback() { | |
this.shadowRoot.removeEventListener('change', this); | |
this.closest('form')?.removeEventListener('formdata', this); | |
} | |
attributeChangedCallback(name) { | |
if (this.value === 0) { | |
this.shadowRoot.querySelector(':checked').checked = false; | |
} else { | |
this.shadowRoot.querySelector(`[value="${this.value}"]`).checked = true; | |
} | |
} | |
handleEvent(event) { | |
switch(event.type) { | |
case 'change': | |
this.setAttribute('value', event.target.value); | |
this.dispatchEvent(new Event('change', { bubbles: true })); | |
break; | |
case 'formdata': | |
// https://web.dev/more-capable-form-controls/ | |
event.formData.append(this.getAttribute('name'), this.getAttribute('value')); | |
break; | |
} | |
} | |
} | |
customElements.define('star-rating', StarRatingElement); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
console.log('Hello World!'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* todo: add styles */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment