Skip to content
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

Add Thumb Label to Slider #976

Merged
merged 5 commits into from
Aug 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/components/slider/slider.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<div class="md-slider-wrapper">
<div class="md-slider-container"
[class.md-slider-sliding]="isSliding"
[class.md-slider-active]="isActive">
[class.md-slider-active]="isActive"
[class.md-slider-thumb-label-showing]="thumbLabel">
<div class="md-slider-track-container">
<div class="md-slider-track"></div>
<div class="md-slider-track md-slider-track-fill"></div>
Expand All @@ -11,6 +12,9 @@
<div class="md-slider-thumb-container">
<div class="md-slider-thumb-position">
<div class="md-slider-thumb"></div>
<div class="md-slider-thumb-label">
<span class="md-slider-thumb-label-text">{{value}}</span>
</div>
</div>
</div>
</div>
Expand Down
59 changes: 55 additions & 4 deletions src/components/slider/slider.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

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

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

$md-slider-thumb-arrow-height: 16px !default;
$md-slider-thumb-arrow-width: 28px !default;

$md-slider-thumb-label-size: 28px !default;
// The thumb has to be moved down so that it appears right over the slider track when visible and
// on the slider track when not.
$md-slider-thumb-label-top: ($md-slider-thickness / 2) -
($md-slider-thumb-default-scale * $md-slider-thumb-size / 2) - $md-slider-thumb-label-size -
$md-slider-thumb-arrow-height + 10px !default;

/**
* Uses a container height and an item height to center an item vertically within the container.
*/
Expand Down Expand Up @@ -137,6 +147,43 @@ md-slider *::after {
border-color: md-color($md-accent);
}

.md-slider-thumb-label {
display: flex;
align-items: center;
justify-content: center;

position: absolute;
left: -($md-slider-thumb-label-size / 2);
top: $md-slider-thumb-label-top;
width: $md-slider-thumb-label-size;
height: $md-slider-thumb-label-size;
border-radius: 50%;

transform: scale(0.4) translate3d(0, (-$md-slider-thumb-label-top + 10) / 0.4, 0) rotate(45deg);
transition: 300ms $swift-ease-in-out-timing-function;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably specify which properties we want to transition. It may not be necessary now, but it can be really annoying to debug if a transition applies to an incompatible property. As was my case with Tooltip a few weeks ago.

transition-property: transform, border-radius;

background-color: md-color($md-accent);
}

.md-slider-thumb-label-text {
z-index: 1;
font-size: 12px;
font-weight: bold;
opacity: 0;
transform: rotate(-45deg);
transition: opacity 300ms $swift-ease-in-out-timing-function;
color: white;
}

.md-slider-container:not(.md-slider-thumb-label-showing) .md-slider-thumb-label {
display: none;
}

.md-slider-active.md-slider-thumb-label-showing .md-slider-thumb {
transform: scale(0);
}

.md-slider-sliding .md-slider-thumb-position,
.md-slider-sliding .md-slider-track-fill {
transition: none;
Expand All @@ -147,7 +194,11 @@ md-slider *::after {
transform: scale($md-slider-thumb-focus-scale);
}

.md-slider-disabled .md-slider-thumb::after {
background-color: $md-slider-disabled-color;
border-color: $md-slider-disabled-color;
.md-slider-active .md-slider-thumb-label {
border-radius: 50% 50% 0;
transform: rotate(45deg);
}

.md-slider-active .md-slider-thumb-label-text {
opacity: 1;
}
97 changes: 90 additions & 7 deletions src/components/slider/slider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ describe('MdSlider', () => {
SliderWithValue,
SliderWithStep,
SliderWithAutoTickInterval,
SliderWithSetTickInterval
SliderWithSetTickInterval,
SliderWithThumbLabel,
],
});

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

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

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

// The closest snap is halfway on the slider.
expect(thumbDimensions.left).toBe(sliderDimensions.width * 0.5 + sliderDimensions.left);
expect(Math.round(trackFillDimensions.width)).toBe(Math.round(thumbPosition));
expect(trackFillDimensions.width).toBe(thumbPosition);
});

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

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

});
});
Expand Down Expand Up @@ -410,7 +411,7 @@ describe('MdSlider', () => {

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

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

// The closest snap is at the end of the slider.
expect(thumbDimensions.left).toBe(sliderDimensions.width + sliderDimensions.left);
expect(Math.round(trackFillDimensions.width)).toBe(Math.round(thumbPosition));
expect(trackFillDimensions.width).toBe(thumbPosition);
});
});

Expand Down Expand Up @@ -516,6 +517,77 @@ describe('MdSlider', () => {
+ 'black 2px, transparent 2px, transparent)');
});
});

describe('slider with thumb label', () => {
let fixture: ComponentFixture<SliderWithThumbLabel>;
let sliderDebugElement: DebugElement;
let sliderNativeElement: HTMLElement;
let sliderInstance: MdSlider;
let sliderTrackElement: HTMLElement;
let sliderContainerElement: Element;
let thumbLabelTextElement: Element;

beforeEach(async(() => {
builder.createAsync(SliderWithThumbLabel).then(f => {
fixture = f;
fixture.detectChanges();

sliderDebugElement = fixture.debugElement.query(By.directive(MdSlider));
sliderNativeElement = sliderDebugElement.nativeElement;
sliderInstance = sliderDebugElement.componentInstance;
sliderTrackElement = <HTMLElement>sliderNativeElement.querySelector('.md-slider-track');
sliderContainerElement = sliderNativeElement.querySelector('.md-slider-container');
thumbLabelTextElement = sliderNativeElement.querySelector('.md-slider-thumb-label-text');
});
}));

it('should add the thumb label class to the slider container', () => {
expect(sliderContainerElement.classList).toContain('md-slider-thumb-label-showing');
});

it('should update the thumb label text on click', () => {
expect(thumbLabelTextElement.textContent).toBe('0');

dispatchClickEvent(sliderTrackElement, 0.13);
fixture.detectChanges();

// The thumb label text is set to the slider's value. These should always be the same.
expect(thumbLabelTextElement.textContent).toBe('13');
});

it('should update the thumb label text on slide', () => {
expect(thumbLabelTextElement.textContent).toBe('0');

dispatchSlideEvent(sliderTrackElement, sliderNativeElement, 0, 0.56, gestureConfig);
fixture.detectChanges();

// The thumb label text is set to the slider's value. These should always be the same.
expect(thumbLabelTextElement.textContent).toBe(`${sliderInstance.value}`);
});

it('should show the thumb label on click', () => {
expect(sliderContainerElement.classList).not.toContain('md-slider-active');
expect(sliderContainerElement.classList).toContain('md-slider-thumb-label-showing');

dispatchClickEvent(sliderNativeElement, 0.49);
fixture.detectChanges();

// The thumb label appears when the slider is active and the 'md-slider-thumb-label-showing'
// class is applied.
expect(sliderContainerElement.classList).toContain('md-slider-thumb-label-showing');
expect(sliderContainerElement.classList).toContain('md-slider-active');
});

it('should show the thumb label on slide', () => {
expect(sliderContainerElement.classList).not.toContain('md-slider-active');

dispatchSlideEvent(sliderTrackElement, sliderNativeElement, 0, 0.91, gestureConfig);
fixture.detectChanges();

expect(sliderContainerElement.classList).toContain('md-slider-thumb-label-showing');
expect(sliderContainerElement.classList).toContain('md-slider-active');
});
});
});

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

@Component({
template: `<md-slider thumb-label></md-slider>`,
styles: [`
.md-slider-thumb-label, .md-slider-thumb-label-text {
Copy link
Member

@jelbourn jelbourn Aug 11, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment for why this is necessary

transition: none !important;
}
`],
encapsulation: ViewEncapsulation.None
})
class SliderWithThumbLabel { }

/**
* Dispatches a click event from an element.
* Note: The mouse event truncates the position for the click.
Expand Down
7 changes: 6 additions & 1 deletion src/components/slider/slider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ export class MdSlider implements AfterContentInit {
@HostBinding('attr.aria-disabled')
disabled: boolean = false;

/** Whether or not to show the thumb label. */
@Input('thumb-label')
@BooleanFieldValue()
thumbLabel: boolean = false;

/** The miniumum value that the slider can have. */
private _min: number = 0;

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

let position = percent * width;
let position = Math.round(percent * width);

fillTrackElement.style.width = `${position}px`;
applyCssTransform(thumbPositionElement, `translateX(${position}px)`);
Expand Down
3 changes: 3 additions & 0 deletions src/demo-app/slider/slider-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ <h1>Slider with step defined</h1>
<h1>Slider with set tick interval</h1>
<md-slider tick-interval="auto"></md-slider>
<md-slider tick-interval="9"></md-slider>

<h1>Slider with Thumb Label</h1>
<md-slider thumb-label></md-slider>