Skip to content

Commit

Permalink
updated visual styling of rating slider, selected rating value and fo…
Browse files Browse the repository at this point in the history
…cus ring
  • Loading branch information
jongund committed Oct 19, 2023
1 parent c3ad48c commit 34b491d
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 142 deletions.
91 changes: 6 additions & 85 deletions content/patterns/slider/examples/css/slider-rating.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,60 +27,21 @@

.rating-slider svg .label {
font-size: 90%;
color: white;
}

.rating-slider svg .description {
font-size: 90%;
}

.rating-slider[aria-valuenow="1"] svg .rating-1 .value {
.rating-slider svg .current .value {
fill: currentcolor;
fill-opacity: 1;
}

.rating-slider[aria-valuenow="2"] svg .rating-2 .value {
fill: currentcolor;
fill-opacity: 1;
}

.rating-slider[aria-valuenow="3"] svg .rating-3 .value {
fill: currentcolor;
fill-opacity: 1;
}

.rating-slider[aria-valuenow="4"] svg .rating-4 .value {
fill: currentcolor;
fill-opacity: 1;
}

.rating-slider[aria-valuenow="5"] svg .rating-5 .value {
fill: currentcolor;
fill-opacity: 1;
}

.rating-slider[aria-valuenow="6"] svg .rating-6 .value {
fill: currentcolor;
fill-opacity: 1;
}

.rating-slider[aria-valuenow="7"] svg .rating-7 .value {
fill: currentcolor;
fill-opacity: 1;
}

.rating-slider[aria-valuenow="8"] svg .rating-8 .value {
fill: currentcolor;
fill-opacity: 1;
}

.rating-slider[aria-valuenow="9"] svg .rating-9 .value {
fill: currentcolor;
fill-opacity: 1;
}

.rating-slider[aria-valuenow="10"] svg .rating-10 .value {
fill: currentcolor;
fill-opacity: 1;
.rating-slider svg .current .label {
fill: white;
font-weight: bold;
}

/* focus styling */
Expand All @@ -96,47 +57,7 @@
fill-opacity: 0;
}

.rating-slider[aria-valuenow="0"]:focus svg .focus-ring {
.rating-slider:focus svg .focus-ring {
stroke-width: 2px;
stroke: currentcolor;
}

.rating-slider[aria-valuenow="1"]:focus svg .rating-1 .focus {
stroke-width: 2px;
}

.rating-slider[aria-valuenow="2"]:focus svg .rating-2 .focus {
stroke-width: 2px;
}

.rating-slider[aria-valuenow="3"]:focus svg .rating-3 .focus {
stroke-width: 2px;
}

.rating-slider[aria-valuenow="4"]:focus svg .rating-4 .focus {
stroke-width: 2px;
}

.rating-slider[aria-valuenow="5"]:focus svg .rating-5 .focus {
stroke-width: 2px;
}

.rating-slider[aria-valuenow="6"]:focus svg .rating-6 .focus {
stroke-width: 2px;
}

.rating-slider[aria-valuenow="7"]:focus svg .rating-7 .focus {
stroke-width: 2px;
}

.rating-slider[aria-valuenow="8"]:focus svg .rating-8 .focus {
stroke-width: 2px;
}

.rating-slider[aria-valuenow="9"]:focus svg .rating-9 .focus {
stroke-width: 2px;
}

.rating-slider[aria-valuenow="10"]:focus svg .rating-10 .focus {
stroke-width: 2px;
}
121 changes: 113 additions & 8 deletions content/patterns/slider/examples/js/slider-rating.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
* Desc: RatingSlider widget that implements ARIA Authoring Practices
*/

const OFFSET_SIZE = 4;

class RatingSlider {
constructor(domNode) {
this.sliderNode = domNode;
Expand All @@ -16,12 +18,37 @@ class RatingSlider {

this.svgNode = domNode.querySelector('svg');

// Inherit system text colors
// var color = getComputedStyle(this.sliderNode).color;
// this.svgNode.setAttribute('color', color);

this.railWidth = 300;
this.railOffset = 30;
this.ratingRects = Array.from(
domNode.querySelectorAll('g.rating rect.value')
);
this.infoRatingRects = [];

this.ratingRects.forEach((r) => {
const info = {
x: parseInt(r.getAttribute('x')),
y: parseInt(r.getAttribute('y')),
width: parseInt(r.getAttribute('width')),
height: parseInt(r.getAttribute('height')),
rx: 0,
};
this.infoRatingRects.push(info);
});

const infoFirstRect = this.infoRatingRects[0];
const infoLastRect = this.infoRatingRects[this.infoRatingRects.length - 1];

this.railOffset = infoFirstRect.x;
this.railWidth = infoLastRect.x + infoLastRect.width - infoFirstRect.x;

this.focusRect = domNode.querySelector('.focus-ring');

this.infoDefaultFocus = {
x: OFFSET_SIZE,
y: OFFSET_SIZE,
width: infoLastRect.x + infoLastRect.width + OFFSET_SIZE,
height: infoFirstRect.y + infoLastRect.height + OFFSET_SIZE,
rx: OFFSET_SIZE,
};

this.valueMin = this.getValueMin();
this.valueMax = this.getValueMax();
Expand Down Expand Up @@ -52,6 +79,8 @@ class RatingSlider {
'blur',
this.addTotalCirclesToRatingLabel.bind(this)
);

this.setFocusRing(0);
}

// Get point in global SVG space
Expand Down Expand Up @@ -157,10 +186,86 @@ class RatingSlider {
return 'Unexpected value: ' + value;
}

resetRects() {
for (let i = 0; i < this.ratingRects.length; i += 1) {
const rect = this.ratingRects[i];
const info = this.infoRatingRects[i];

rect.setAttribute('x', info.x);
rect.setAttribute('y', info.y);
rect.setAttribute('width', info.width);
rect.setAttribute('height', info.height);
rect.setAttribute('rx', info.rx);

rect.parentNode.classList.remove('current');
}
}

setSelectedRating(value) {
let rect, info;

const leftValue = value - 1;
const rightValue = value + 1;

if (value > 0) {
rect = this.ratingRects[value - 1];
info = this.infoRatingRects[value - 1];

rect.setAttribute('x', info.x - OFFSET_SIZE);
rect.setAttribute('y', info.y - OFFSET_SIZE);
rect.setAttribute('width', info.width + 2 * OFFSET_SIZE);
rect.setAttribute('height', info.height + 2 * OFFSET_SIZE);
rect.setAttribute('rx', OFFSET_SIZE);

rect.parentNode.classList.add('current');
}

if (leftValue > 0) {
rect = this.ratingRects[leftValue - 1];
info = this.infoRatingRects[leftValue - 1];

rect.setAttribute('width', info.width - OFFSET_SIZE);
}

if (rightValue <= this.valueMax) {
rect = this.ratingRects[rightValue - 1];
info = this.infoRatingRects[rightValue - 1];

rect.setAttribute('x', info.x + OFFSET_SIZE);
rect.setAttribute('width', info.width - OFFSET_SIZE);
}
}

setFocusRing(value) {
const size = 2 * OFFSET_SIZE;

if (value > 0 && value <= this.valueMax) {
const info = this.infoRatingRects[value - 1];

this.focusRect.setAttribute('x', info.x - size);
this.focusRect.setAttribute('y', info.y - size);
this.focusRect.setAttribute('width', info.width + 2 * size);
this.focusRect.setAttribute('height', info.height + 2 * size);
this.focusRect.setAttribute('rx', size);
} else {
// Set ring around entire control

this.focusRect.setAttribute('x', this.infoDefaultFocus.x);
this.focusRect.setAttribute('y', this.infoDefaultFocus.y);
this.focusRect.setAttribute('width', this.infoDefaultFocus.width);
this.focusRect.setAttribute('height', this.infoDefaultFocus.height);
this.focusRect.setAttribute('rx', this.infoDefaultFocus.rx);
}
}

moveSliderTo(value) {
value = Math.min(Math.max(value, this.valueMin + 1), this.valueMax);
this.sliderNode.setAttribute('aria-valuenow', value);
this.sliderNode.setAttribute('aria-valuetext', this.getValueText(value));

this.resetRects();
this.setSelectedRating(value);
this.setFocusRing(value);
}

onSliderKeydown(event) {
Expand Down Expand Up @@ -219,7 +324,7 @@ class RatingSlider {
const x = this.getSVGPoint(event).x;
const diffX = x - this.railOffset;
const rating = (diffX * this.valueMax) / this.railWidth;
const value = Math.floor(rating);
const value = Math.ceil(rating);

this.moveSliderTo(value);

Expand All @@ -245,7 +350,7 @@ class RatingSlider {
const x = this.getSVGPoint(event).x;
const diffX = x - this.railOffset;
const rating = (diffX * this.valueMax) / this.railWidth;
const value = Math.floor(rating);
const value = Math.ceil(rating);

this.moveSliderTo(value);

Expand Down
86 changes: 37 additions & 49 deletions content/patterns/slider/examples/slider-rating.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,61 +71,49 @@ <h2 id="ex_label">Example</h2>
aria-labelledby="id-rating-label">
<!-- SVG position and dimension values added through Javascript -->

<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="386" height="96">
<rect class="focus-ring" x="23" y="2" width="358" height="90" rx="5" />
<g class="rating-1">
<circle class="value" cx="66" cy="20" r="10"/>
<circle class="focus" cx="66" cy="20" r="14"/>
<text class="label" x="62" y="50">1</text>
<text class="description" x="34" y="65">Extremely</text>
<text class="description" x="31" y="80">Unsatisfied</text>
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="520" height="96">
<rect class="focus-ring" x="23" y="2" width="54" height="90" rx="5" />
<g class="rating">
<rect class="value" height="24" width="48" x="13" y="30"></rect>
<text class="label" x="32" y="47">1</text>
<text class="description" x="13" y="20">Extremely Unsatisfied</text>
</g>
<g class="rating-2">
<circle class="value" cx="96" cy="20" r="10"/>
<circle class="focus" cx="96" cy="20" r="14"/>
<text class="label" x="92" y="50">2</text>
<g class="rating">
<rect class="value" height="24" width="48" x="61" y="30"></rect>
<text class="label" x="80" y="47">2</text>
</g>
<g class="rating-3">
<circle class="value" cx="126" cy="20" r="10"/>
<circle class="focus" cx="126" cy="20" r="14"/>
<text class="label" x="120" y="50">3</text>
<g class="rating">
<rect class="value" height="24" width="48" x="109" y="30"></rect>
<text class="label" x="128" y="47">3</text>
</g>
<g class="rating-4">
<circle class="value" cx="156" cy="20" r="10"/>
<circle class="focus" cx="156" cy="20" r="14"/>
<text class="label" x="152" y="50">4</text>
<g class="rating">
<rect class="value" height="24" width="48" x="157" y="30"></rect>
<text class="label" x="176" y="47">4</text>
</g>
<g class="rating-5">
<circle class="value" cx="186" cy="20" r="10"/>
<circle class="focus" cx="186" cy="20" r="14"/>
<text class="label" x="182" y="50">5</text>
<g class="rating">
<rect class="value" height="24" width="48" x="205" y="30"></rect>
<text class="label" x="224" y="47">5</text>
</g>
<g class="rating-6">
<circle class="value" cx="216" cy="20" r="10"/>
<circle class="focus" cx="216" cy="20" r="14"/>
<text class="label" x="212" y="50">6</text>
<g class="rating">
<rect class="value" height="24" width="48" x="253" y="30"></rect>
<text class="label" x="272" y="47">6</text>
</g>
<g class="rating-7">
<circle class="value" cx="246" cy="20" r="10"/>
<circle class="focus" cx="246" cy="20" r="14"/>
<text class="label" x="242" y="50">7</text>
<g class="rating">
<rect class="value" height="24" width="48" x="301" y="30"></rect>
<text class="label" x="320" y="47">7</text>
</g>
<g class="rating-8">
<circle class="value" cx="276" cy="20" r="10"/>
<circle class="focus" cx="276" cy="20" r="14"/>
<text class="label" x="272" y="50">8</text>
<g class="rating">
<rect class="value" height="24" width="48" x="349" y="30"></rect>
<text class="label" x="368" y="47">8</text>
</g>
<g class="rating-9">
<circle class="value" cx="306" cy="20" r="10"/>
<circle class="focus" cx="306" cy="20" r="14"/>
<text class="label" x="302" y="50">9</text>
<g class="rating">
<rect class="value" height="24" width="48" x="397" y="30"></rect>
<text class="label" x="416" y="47">9</text>
</g>
<g class="rating-10">
<circle class="value" cx="336" cy="20" r="10"/>
<circle class="focus" cx="336" cy="20" r="14"/>
<text class="label" x="328" y="50">10</text>
<text class="description" x="306" y="65">Extremely</text>
<text class="description" x="311" y="80">Satisfied</text>
<g class="rating">
<rect class="value" height="24" width="48" x="445" y="30"></rect>
<text class="label" x="458" y="47">10</text>
<text class="description" x="369" y="20">Extremely Satisfied</text>
</g>
</svg>
</div>
Expand All @@ -140,10 +128,10 @@ <h2>Accessibility Features</h2>
To ensure assistive technology users correctly perceive the maximum slider value, this example uses the <code>aria-valuetext</code> property to communicate both the current and maximum values.
However, since repeating the maximum value every time the slider value changes is potentially distracting, the maximum value is included in <code>aria-valuetext</code> only when the slider is initialized and when the thumb loses keyboard focus.
</li>
<li>To highlight the interactive nature of the satisfaction rating, a focus ring appears around the group of rating options when the thumb has focus and no rating value has been selected.</li>
<li>To highlight the interactive nature of the satisfaction rating, a focus ring appears around the group of rating options when the slider has focus and no rating value has been selected.</li>
<li>
To ensure the borders of the circles and focus ring have sufficient contrast with the background when high contrast settings invert colors, the color of the borders are synchronized with the color of the text content.
For example, the color of the circle borders is set to match the foreground color of high contrast mode text by specifying the CSS <code>currentcolor</code> value for the <code>stroke</code> property of each inline SVG <code>circle</code> and <code>text</code> elements.
To ensure the borders of the rating values and focus ring have sufficient contrast with the background when high contrast settings invert colors, the color of the borders are synchronized with the color of the text content.
For example, the color of the rating value borders is set to match the foreground color of high contrast mode text by specifying the CSS <code>currentcolor</code> value for the <code>stroke</code> property of each inline SVG <code>rect</code> and <code>text</code> elements.
If specific colors were used to specify the <code>stroke</code> and <code>fill</code> properties, the color of these elements would remain the same in high contrast mode, which could lead to insufficient contrast between them and their background or even make them invisible if their color were to match the high contrast mode background.<br>
Note: The SVG element needs to have the CSS <code>forced-color-adjust</code> property set to the value <code>auto</code> for the <code>currentcolor</code> value to be updated in high contrast modes.
Some browsers do not use <code>auto</code> for the default value.</li>
Expand Down

0 comments on commit 34b491d

Please sign in to comment.