Skip to content

Commit 22d70ae

Browse files
iveysaurjelbourn
authored andcommitted
feat(slider): add thumb-label (#976)
1 parent 844a213 commit 22d70ae

File tree

5 files changed

+159
-13
lines changed

5 files changed

+159
-13
lines changed

src/components/slider/slider.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<div class="md-slider-wrapper">
22
<div class="md-slider-container"
33
[class.md-slider-sliding]="isSliding"
4-
[class.md-slider-active]="isActive">
4+
[class.md-slider-active]="isActive"
5+
[class.md-slider-thumb-label-showing]="thumbLabel">
56
<div class="md-slider-track-container">
67
<div class="md-slider-track"></div>
78
<div class="md-slider-track md-slider-track-fill"></div>
@@ -11,6 +12,9 @@
1112
<div class="md-slider-thumb-container">
1213
<div class="md-slider-thumb-position">
1314
<div class="md-slider-thumb"></div>
15+
<div class="md-slider-thumb-label">
16+
<span class="md-slider-thumb-label-text">{{value}}</span>
17+
</div>
1418
</div>
1519
</div>
1620
</div>

src/components/slider/slider.scss

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
// This refers to the thickness of the slider. On a horizontal slider this is the height, on a
55
// vertical slider this is the width.
6-
$md-slider-thickness: 20px !default;
6+
$md-slider-thickness: 48px !default;
77
$md-slider-min-size: 128px !default;
88
$md-slider-padding: 8px !default;
99

@@ -18,6 +18,16 @@ $md-slider-off-color: rgba(black, 0.26);
1818
$md-slider-focused-color: rgba(black, 0.38);
1919
$md-slider-disabled-color: rgba(black, 0.26);
2020

21+
$md-slider-thumb-arrow-height: 16px !default;
22+
$md-slider-thumb-arrow-width: 28px !default;
23+
24+
$md-slider-thumb-label-size: 28px !default;
25+
// The thumb has to be moved down so that it appears right over the slider track when visible and
26+
// on the slider track when not.
27+
$md-slider-thumb-label-top: ($md-slider-thickness / 2) -
28+
($md-slider-thumb-default-scale * $md-slider-thumb-size / 2) - $md-slider-thumb-label-size -
29+
$md-slider-thumb-arrow-height + 10px !default;
30+
2131
/**
2232
* Uses a container height and an item height to center an item vertically within the container.
2333
*/
@@ -137,6 +147,43 @@ md-slider *::after {
137147
border-color: md-color($md-accent);
138148
}
139149

150+
.md-slider-thumb-label {
151+
display: flex;
152+
align-items: center;
153+
justify-content: center;
154+
155+
position: absolute;
156+
left: -($md-slider-thumb-label-size / 2);
157+
top: $md-slider-thumb-label-top;
158+
width: $md-slider-thumb-label-size;
159+
height: $md-slider-thumb-label-size;
160+
border-radius: 50%;
161+
162+
transform: scale(0.4) translate3d(0, (-$md-slider-thumb-label-top + 10) / 0.4, 0) rotate(45deg);
163+
transition: 300ms $swift-ease-in-out-timing-function;
164+
transition-property: transform, border-radius;
165+
166+
background-color: md-color($md-accent);
167+
}
168+
169+
.md-slider-thumb-label-text {
170+
z-index: 1;
171+
font-size: 12px;
172+
font-weight: bold;
173+
opacity: 0;
174+
transform: rotate(-45deg);
175+
transition: opacity 300ms $swift-ease-in-out-timing-function;
176+
color: white;
177+
}
178+
179+
.md-slider-container:not(.md-slider-thumb-label-showing) .md-slider-thumb-label {
180+
display: none;
181+
}
182+
183+
.md-slider-active.md-slider-thumb-label-showing .md-slider-thumb {
184+
transform: scale(0);
185+
}
186+
140187
.md-slider-sliding .md-slider-thumb-position,
141188
.md-slider-sliding .md-slider-track-fill {
142189
transition: none;
@@ -147,7 +194,11 @@ md-slider *::after {
147194
transform: scale($md-slider-thumb-focus-scale);
148195
}
149196

150-
.md-slider-disabled .md-slider-thumb::after {
151-
background-color: $md-slider-disabled-color;
152-
border-color: $md-slider-disabled-color;
197+
.md-slider-active .md-slider-thumb-label {
198+
border-radius: 50% 50% 0;
199+
transform: rotate(45deg);
200+
}
201+
202+
.md-slider-active .md-slider-thumb-label-text {
203+
opacity: 1;
153204
}

src/components/slider/slider.spec.ts

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ describe('MdSlider', () => {
2626
SliderWithValue,
2727
SliderWithStep,
2828
SliderWithAutoTickInterval,
29-
SliderWithSetTickInterval
29+
SliderWithSetTickInterval,
30+
SliderWithThumbLabel,
3031
],
3132
});
3233

@@ -118,7 +119,7 @@ describe('MdSlider', () => {
118119
// offset relative to the track, subtract the offset on the track fill.
119120
let thumbPosition = thumbDimensions.left - trackFillDimensions.left;
120121
// The track fill width should be equal to the thumb's position.
121-
expect(Math.round(trackFillDimensions.width)).toBe(Math.round(thumbPosition));
122+
expect(trackFillDimensions.width).toBe(thumbPosition);
122123
});
123124

124125
it('should update the thumb position on click', () => {
@@ -144,7 +145,7 @@ describe('MdSlider', () => {
144145
// offset relative to the track, subtract the offset on the track fill.
145146
let thumbPosition = thumbDimensions.left - trackFillDimensions.left;
146147
// The track fill width should be equal to the thumb's position.
147-
expect(Math.round(trackFillDimensions.width)).toBe(Math.round(thumbPosition));
148+
expect(trackFillDimensions.width).toBe(thumbPosition);
148149
});
149150

150151
it('should update the thumb position on slide', () => {
@@ -309,7 +310,7 @@ describe('MdSlider', () => {
309310

310311
// The closest snap is halfway on the slider.
311312
expect(thumbDimensions.left).toBe(sliderDimensions.width * 0.5 + sliderDimensions.left);
312-
expect(Math.round(trackFillDimensions.width)).toBe(Math.round(thumbPosition));
313+
expect(trackFillDimensions.width).toBe(thumbPosition);
313314
});
314315

315316
it('should snap the thumb and fill to the nearest value on slide', () => {
@@ -325,7 +326,7 @@ describe('MdSlider', () => {
325326

326327
// The closest snap is at the halfway point on the slider.
327328
expect(thumbDimensions.left).toBe(sliderDimensions.left + sliderDimensions.width * 0.5);
328-
expect(Math.round(trackFillDimensions.width)).toBe(Math.round(thumbPosition));
329+
expect(trackFillDimensions.width).toBe(thumbPosition);
329330

330331
});
331332
});
@@ -410,7 +411,7 @@ describe('MdSlider', () => {
410411

411412
// The closest step is at 75% of the slider.
412413
expect(thumbDimensions.left).toBe(sliderDimensions.width * 0.75 + sliderDimensions.left);
413-
expect(Math.round(trackFillDimensions.width)).toBe(Math.round(thumbPosition));
414+
expect(trackFillDimensions.width).toBe(thumbPosition);
414415
});
415416

416417
it('should set the correct step value on slide', () => {
@@ -433,7 +434,7 @@ describe('MdSlider', () => {
433434

434435
// The closest snap is at the end of the slider.
435436
expect(thumbDimensions.left).toBe(sliderDimensions.width + sliderDimensions.left);
436-
expect(Math.round(trackFillDimensions.width)).toBe(Math.round(thumbPosition));
437+
expect(trackFillDimensions.width).toBe(thumbPosition);
437438
});
438439
});
439440

@@ -516,6 +517,77 @@ describe('MdSlider', () => {
516517
+ 'black 2px, transparent 2px, transparent)');
517518
});
518519
});
520+
521+
describe('slider with thumb label', () => {
522+
let fixture: ComponentFixture<SliderWithThumbLabel>;
523+
let sliderDebugElement: DebugElement;
524+
let sliderNativeElement: HTMLElement;
525+
let sliderInstance: MdSlider;
526+
let sliderTrackElement: HTMLElement;
527+
let sliderContainerElement: Element;
528+
let thumbLabelTextElement: Element;
529+
530+
beforeEach(async(() => {
531+
builder.createAsync(SliderWithThumbLabel).then(f => {
532+
fixture = f;
533+
fixture.detectChanges();
534+
535+
sliderDebugElement = fixture.debugElement.query(By.directive(MdSlider));
536+
sliderNativeElement = sliderDebugElement.nativeElement;
537+
sliderInstance = sliderDebugElement.componentInstance;
538+
sliderTrackElement = <HTMLElement>sliderNativeElement.querySelector('.md-slider-track');
539+
sliderContainerElement = sliderNativeElement.querySelector('.md-slider-container');
540+
thumbLabelTextElement = sliderNativeElement.querySelector('.md-slider-thumb-label-text');
541+
});
542+
}));
543+
544+
it('should add the thumb label class to the slider container', () => {
545+
expect(sliderContainerElement.classList).toContain('md-slider-thumb-label-showing');
546+
});
547+
548+
it('should update the thumb label text on click', () => {
549+
expect(thumbLabelTextElement.textContent).toBe('0');
550+
551+
dispatchClickEvent(sliderTrackElement, 0.13);
552+
fixture.detectChanges();
553+
554+
// The thumb label text is set to the slider's value. These should always be the same.
555+
expect(thumbLabelTextElement.textContent).toBe('13');
556+
});
557+
558+
it('should update the thumb label text on slide', () => {
559+
expect(thumbLabelTextElement.textContent).toBe('0');
560+
561+
dispatchSlideEvent(sliderTrackElement, sliderNativeElement, 0, 0.56, gestureConfig);
562+
fixture.detectChanges();
563+
564+
// The thumb label text is set to the slider's value. These should always be the same.
565+
expect(thumbLabelTextElement.textContent).toBe(`${sliderInstance.value}`);
566+
});
567+
568+
it('should show the thumb label on click', () => {
569+
expect(sliderContainerElement.classList).not.toContain('md-slider-active');
570+
expect(sliderContainerElement.classList).toContain('md-slider-thumb-label-showing');
571+
572+
dispatchClickEvent(sliderNativeElement, 0.49);
573+
fixture.detectChanges();
574+
575+
// The thumb label appears when the slider is active and the 'md-slider-thumb-label-showing'
576+
// class is applied.
577+
expect(sliderContainerElement.classList).toContain('md-slider-thumb-label-showing');
578+
expect(sliderContainerElement.classList).toContain('md-slider-active');
579+
});
580+
581+
it('should show the thumb label on slide', () => {
582+
expect(sliderContainerElement.classList).not.toContain('md-slider-active');
583+
584+
dispatchSlideEvent(sliderTrackElement, sliderNativeElement, 0, 0.91, gestureConfig);
585+
fixture.detectChanges();
586+
587+
expect(sliderContainerElement.classList).toContain('md-slider-thumb-label-showing');
588+
expect(sliderContainerElement.classList).toContain('md-slider-active');
589+
});
590+
});
519591
});
520592

521593
// The transition has to be removed in order to test the updated positions without setTimeout.
@@ -572,6 +644,17 @@ class SliderWithAutoTickInterval { }
572644
})
573645
class SliderWithSetTickInterval { }
574646

647+
@Component({
648+
template: `<md-slider thumb-label></md-slider>`,
649+
styles: [`
650+
.md-slider-thumb-label, .md-slider-thumb-label-text {
651+
transition: none !important;
652+
}
653+
`],
654+
encapsulation: ViewEncapsulation.None
655+
})
656+
class SliderWithThumbLabel { }
657+
575658
/**
576659
* Dispatches a click event from an element.
577660
* Note: The mouse event truncates the position for the click.

src/components/slider/slider.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ export class MdSlider implements AfterContentInit {
4747
@HostBinding('attr.aria-disabled')
4848
disabled: boolean = false;
4949

50+
/** Whether or not to show the thumb label. */
51+
@Input('thumb-label')
52+
@BooleanFieldValue()
53+
thumbLabel: boolean = false;
54+
5055
/** The miniumum value that the slider can have. */
5156
private _min: number = 0;
5257

@@ -341,7 +346,7 @@ export class SliderRenderer {
341346
<HTMLElement>this._sliderElement.querySelector('.md-slider-thumb-position');
342347
let fillTrackElement = <HTMLElement>this._sliderElement.querySelector('.md-slider-track-fill');
343348

344-
let position = percent * width;
349+
let position = Math.round(percent * width);
345350

346351
fillTrackElement.style.width = `${position}px`;
347352
applyCssTransform(thumbPositionElement, `translateX(${position}px)`);

src/demo-app/slider/slider-demo.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,6 @@ <h1>Slider with step defined</h1>
3030
<h1>Slider with set tick interval</h1>
3131
<md-slider tick-interval="auto"></md-slider>
3232
<md-slider tick-interval="9"></md-slider>
33+
34+
<h1>Slider with Thumb Label</h1>
35+
<md-slider thumb-label></md-slider>

0 commit comments

Comments
 (0)