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

MultiRangeSlider component #2407

Closed
wants to merge 27 commits into from
Closed

MultiRangeSlider component #2407

wants to merge 27 commits into from

Conversation

jscheiny
Copy link
Contributor

@jscheiny jscheiny commented Apr 19, 2018

Checklist


Changes proposed in this pull request:

This adds a very general MultiRangeSlider component that ultimately should be able to subsume the Slider and RangeSlider components.

Notes about the component API:

  • Instead of taking a value/values the component takes a list of handles specified by props. Each handle has:
  • value - where it gets placed on the slider
  • type - specifies how the slider handle should be displayed
    • "full" - handle appears like it does in a Slider (default)
    • "lower" - handle appears like the first slider in a RangeSlider
    • "upper" - handle appears like the second slider in a RangeSlider
  • trackIntent{Above,Below} specifies the color of the track fill above and below (numerically) of the given handle.

Handles are always displayed in ascending numeric order and the values that are returned by onChange and onRelease are always sorted.

In addition, MultiRangeSlider takes a defaultTrackIntent prop which is used in the resolution of how to render the color of the track between two given handles. The way that this is resolved is as follows (and is one of my concerns about the API).

  • If the left handle has an above intent, use that
  • If the right handle has a below intent, use that
  • If the component has a default intent, use that
  • Otherwise don't color that section

Important: Note that there is a difference between the NONE intent and the absence of an intent entirely (undefined). The former will render a gray track between the two handles and the latter will fall-through to the next color in the hierarchy above.

I previously allowed the specification of colors instead of intents but that didn't seem very blueprint-y.


Reviewers should focus on:

I have explicitly not written tests/docs for this yet, as I would first like to iterate on the API as I'm not convinced what I've written is particularly nice (worked for our use case but don't know if it generalizes well). Once we've iterated and nailed down an API I will gladly write tests and docs for this component.

I've written some questions in the code, but one additional question I have is should this component live in labs first to incubate before moving into core?


Not addressed in this PR (requires follow-up):

To keep this PR simple(r) and easy(er) to review, I have not made many changes to the existing slider components but in 1-2 follow up PRs I would like to:

  1. Refactor Slider and RangeSlider to be simple wrappers around MultiRangeSlider
  2. Remove CoreSlider and refactor into MultiRangeSlider

The second one is questionable, especially if there are plans to add a slider that does not fit into the multi range slider model.


Screenshots

Two tailed slider:

screen shot 2018-04-19 at 10 43 24 am

Disabled:

screen shot 2018-04-19 at 11 09 44 am

One tailed:

screen shot 2018-04-19 at 11 10 39 am

Weird:

screen shot 2018-04-19 at 11 12 27 am

Vertical:

screen shot 2018-04-19 at 11 14 13 am

In action:

mulit-range-slider

@palantirtech
Copy link
Member

Thanks for your interest in palantir/blueprint, @jscheiny! Before we can accept your pull request, you need to sign our contributor license agreement - just visit https://cla.palantir.com/ and follow the instructions. Once you sign, I'll automatically update this pull request.

border-bottom-right-radius: 0;
}

&.pt-intent-primary {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not a sass wizard, and I imagine that there's a much nicer way of doing these intent blocks. Any thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

you can loop through all the intents. check out how buttons, callouts, and other intent-supported components do it

handle: (el: HTMLSpanElement) => (this.handleElement = el),
handle: (el: HTMLSpanElement) => {
this.handleElement = el;
this.forceUpdate();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is here because when the number of handles changes, these handles are re-instantiated. When that happens the first render of the component does not have the ref captured and thus incorrectly positions the handle. This fixes by immediately re-rendering once the ref is captured, not sure if there's a nicer way to handle (haha) this. Otherwise, will add a comment to this effect.

@llorca
Copy link
Contributor

llorca commented Apr 19, 2018

one hell of a first time contrib, @jscheiny 🤯 😍

@jscheiny
Copy link
Contributor Author

I like to make an entrance 😄

@jscheiny
Copy link
Contributor Author

Use children instead of array of props

Preview: documentation | landing | table

@jscheiny
Copy link
Contributor Author

Some minor styling issues I found:

When a lower handle with its above track having no intent is moved to the end of the slider, the border radius can be seen through slightly:
screen shot 2018-04-19 at 10 33 45 am

Same problem at the other end (upper handle with no below track color):
screen shot 2018-04-19 at 10 33 49 am

@llorca This is pretty minor, worth fixing?

export const SLIDER_PROGRESS = "pt-slider-progress";
export const RANGE_SLIDER = "pt-range-slider";
export const MULTI_RANGE_SLIDER = "pt-multi-range-slider";
export const LOWER = "pt-lower";
Copy link
Contributor

Choose a reason for hiding this comment

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

what about start and end? we use this vocabulary in other places already (more generic), and it seems to make sense as well here. In addition, lower and upper imply a vertical axis, whereas sliders can be either horizontal or vertical

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Also changed "above" and "below" to "after" and "before"

@llorca
Copy link
Contributor

llorca commented Apr 19, 2018

@jscheiny yes please fix. an easy way would be to shave 1-2px off the max left or right a handle can be, so that it's always on top of the track border radii. I would not suggest modifying the border radius depending on the position of the handles

@jscheiny
Copy link
Contributor Author

Fix lint?

Preview: documentation | landing | table

@llorca
Copy link
Contributor

llorca commented Apr 19, 2018

@jscheiny even though the API isn't locked down, this is clearly an improvement and we'll work with you to get it in. Can you set up a quick docs section for Multi-range slider, with just examples so we can test it out? We can write the full docs later.

@jscheiny
Copy link
Contributor Author

Merge remote-tracking branch 'upstream/develop' into review/js/multi-range-slider

Preview: documentation | landing | table

@jscheiny
Copy link
Contributor Author

Simplify intent coloring

Preview: documentation | landing | table

@jscheiny
Copy link
Contributor Author

@llorca I'd be happy to add an example. However, I can't seem to get markdown changes to compile locally so I can't test that what I'm doing works. I'm running with yarn dev:docs and whenever I make changes to .md files there's no change in the UI. Any thoughts?

@llorca
Copy link
Contributor

llorca commented Apr 20, 2018

@jscheiny hmm yeah I have this issue sometimes too. what about yarn dev:core? if it's still doesn't work, it'll be a @giladgray question

@jscheiny
Copy link
Contributor Author

I was able to do it by running yarn compile in the docs-data package after every markdown change. It would be good to know if there's a nicer way of doing this and, if so, document it.

Anyways @llorca there should be a multi-range slider example now.

@jscheiny
Copy link
Contributor Author

Example component

Preview: documentation | landing | table

@giladgray
Copy link
Contributor

@jscheiny run yarn docs-data in the root to re-compile only the docs json file. there's no watcher for it as it's like way too many files and soooo slow (30+ seconds) so i made a root task to simplify the process.

@jscheiny
Copy link
Contributor Author

Remove import

Preview: documentation | landing | table


export interface ISliderHandleProps {
value: number;
trackIntentAbove?: Intent;
Copy link
Contributor

Choose a reason for hiding this comment

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

className? and trackClassName? would allow custom styling (like non-intent track colors)

handle: (el: HTMLSpanElement) => {
this.handleElement = el;
// TODO If this ends up being the approved way of working, only call this function on the first ref and add
// a comment explaning why.
Copy link
Contributor

Choose a reason for hiding this comment

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

if it's just on the first ref, then it should be in componentDidMount

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can do that, did you see my other comment on why this is necessary / have any thoughts on how to remedy?

let minValue = values[0];
let minArg = argFn(minValue);

for (let index = 1; index < values.length; index++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Math.min(...spread an array)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I needed something equivalent of _.minBy, i.e. find the min of an array as determined by some function.

).filter(child => child !== null);
}

function compare(left: number, right: number) {
Copy link
Contributor

Choose a reason for hiding this comment

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

used once, i suggest inlining the subtraction

import { ISliderHandleProps, SliderHandle } from "./sliderHandle";

export interface IMultiRangeSliderProps extends ICoreSliderProps {
children?: React.ReactNode;
Copy link
Contributor

Choose a reason for hiding this comment

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

this is not necessary as it's the same as React's definition. what would be awesome, since it's validated below, is to declare something along the lines of children?: React.ReactElement<ISliderHandleProps>[]


export type SliderHandleType = "full" | "start" | "end";

export interface ISliderHandleProps {
Copy link
Contributor

Choose a reason for hiding this comment

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

className? and trackClassName? would allow custom styling (like non-intent track colors)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure what className styling on the handle itself would do. Happy to do the class name suggestion, what do you think trackAboveClassName and trackBelowClassName? I found it pretty important in my usage of this component to be able to specify for certain handles the color on the track on one side or the other of it.

Should we still have intent props or just expect people to pass in Classes.INTENT_PRIMARY etc.?

type?: SliderHandleType;
}

export class SliderHandle extends AbstractPureComponent<ISliderHandleProps> {
Copy link
Contributor

Choose a reason for hiding this comment

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

can just use React.PureComponent here as you're not using any of the APC features

@jscheiny
Copy link
Contributor Author

Address comments

Preview: documentation | landing | table

@jscheiny
Copy link
Contributor Author

Refactor slider tests

Preview: documentation | landing | table

@jscheiny
Copy link
Contributor Author

jscheiny commented May 1, 2018

Merge branch 'develop' into js/multi-range-slider

Preview: documentation | landing | table

@jscheiny
Copy link
Contributor Author

jscheiny commented May 1, 2018

Merge branch 'js/multi-range-slider' of github.com:jscheiny/blueprint into review/js/multi-range-slider

Preview: documentation | landing | table

@jscheiny
Copy link
Contributor Author

jscheiny commented May 2, 2018

Add default track intent

Preview: documentation | landing | table

@jscheiny
Copy link
Contributor Author

jscheiny commented May 2, 2018

Fix vertical track border radii

Preview: documentation | landing | table

@jscheiny
Copy link
Contributor Author

jscheiny commented May 3, 2018

Fix lint

Preview: documentation | landing | table

@giladgray
Copy link
Contributor

@jscheiny mind re-opening this as a branch in this repo so I can contribute as well?

@jscheiny jscheiny mentioned this pull request May 22, 2018
3 tasks
@jscheiny
Copy link
Contributor Author

Closing this in favor of #2536

@jscheiny jscheiny closed this May 22, 2018
@jscheiny jscheiny deleted the js/multi-range-slider branch May 22, 2018 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants