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

feat(slider): create slider #1503

Merged
merged 22 commits into from
Feb 28, 2023
Merged

feat(slider): create slider #1503

merged 22 commits into from
Feb 28, 2023

Conversation

jinlee93
Copy link
Contributor

[EDS-818]

Summary:

  • motivation:
    • GST presented a need for a input component with a moving thumb along a track (aka "Slider")
    • Discussed how to display the value of the slider
      • May have to rely on consumer but will show recipes
        • Could be tooltip
        • Could be labels on ends
  • implementation:
    • Usage of semantic <input type="range"> element
    • Usage of pseudo elements to style the thumb and the track for different browsers
      • Alternatively could use non semantic elements and overlay elements ourselves, but would have to put more time into accessibility considerations

Test Plan:

  • Wrote automated tests
  • CI tests / new tests are not applicable
  • Manually tested my changes, but I want to keep the details secret
  • Manually tested my changes, and here are the details:
    • Create an alpha publish and try out in edu-stack or traject as a sanity check if changes affect build or deploy, or are breaking, such as token changes, widely used component updates, hooks changes, and major dependency upgrades.

Screenshot 2023-02-17 at 4 30 09 PM
Screenshot 2023-02-17 at 4 29 51 PM

@jinlee93 jinlee93 requested a review from a team February 18, 2023 00:30
@codecov
Copy link

codecov bot commented Feb 18, 2023

Codecov Report

Merging #1503 (4b3ee02) into next (39d063f) will increase coverage by 0.06%.
The diff coverage is 95.23%.

@@            Coverage Diff             @@
##             next    #1503      +/-   ##
==========================================
+ Coverage   91.91%   91.97%   +0.06%     
==========================================
  Files         277      281       +4     
  Lines        4180     4264      +84     
  Branches      755      781      +26     
==========================================
+ Hits         3842     3922      +80     
- Misses        313      317       +4     
  Partials       25       25              
Impacted Files Coverage Δ
src/components/Slider/Slider.stories.tsx 89.65% <89.65%> (ø)
src/util/findLowestTenMultiplier.ts 90.00% <90.00%> (ø)
src/components/Slider/Slider.tsx 100.00% <100.00%> (ø)
src/components/Slider/index.ts 100.00% <100.00%> (ø)
src/index.ts 100.00% <100.00%> (ø)

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@github-actions
Copy link

github-actions bot commented Feb 18, 2023

size-limit report 📦

Path Size
components 119.72 KB (+1.57% 🔺)
styles 3.1 KB (0%)

height: var(--slider-thumb-size);
}
.slider__input:focus {
outline: none;
Copy link
Contributor Author

@jinlee93 jinlee93 Feb 18, 2023

Choose a reason for hiding this comment

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

This removes the focus from the entire slider to focus just the thumb

Copy link
Contributor

Choose a reason for hiding this comment

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

Is it worth writing a play function that tabs onto the input so the focus is captured by chromatic or is that overkill?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ya, I think it's worth snapping!
Would it replace the default snap or should I make a new story for focus purpose?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, can a person focus on the slider without focusing on the handle? how many tab presses does it take to go to the next focusable element? if it's more than one, we wouldn't want to remove this (necessarily... i'm not sure what best practice is in this case)

I recommend having any focus tests as separate from default, as the default state of any element isn't focused

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Found that the entirety of the slider is interactable, but made the hit box 44px high so it will be AAA compliant. Focus indicator remains on the thumb, however, and is snapped on chromatic

* Slider Input Track
*/
/* Firefox */
.slider__input::-moz-range-track {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Main concern is using these pseudo elements

Copy link
Contributor

@jeremiah-clothier jeremiah-clothier left a comment

Choose a reason for hiding this comment

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

May have to rely on consumer but will show recipes

  • Could be tooltip
  • Could be labels on ends

It would be nice to also have markers/tracks with labels on the sliders (not just the end). I have a few questions about how do recipes typically work for EDS.

  1. Are these just stories in Slider.stories.tsx are the stored elsewhere?
  2. Are recipes exported from EDS or just purely examples to mimic in other repositories

@jinlee93
Copy link
Contributor Author

May have to rely on consumer but will show recipes

  • Could be tooltip
  • Could be labels on ends

It would be nice to also have markers/tracks with labels on the sliders. I have a few questions about how do recipes typically work for EDS.

  1. Are these just stories in Slider.stories.tsx are the stored elsewhere?
  2. Are recipes exported from EDS or just purely examples to mimic in other repositories

I also agree that it would be nice to provide markers for the slider, but using the native list markers doesn't align with <datalist> out of the box, and the aligning the markers with the center of the marker labels would differ depending on the number of markers, width of the marker labels, emoji, etc. Also we would have to consider a case where there are a lot of options (like a volume slider) where a tooltip may be a more useful visual indicator, and thus we tentatively arrived at not having markers out of the box (may change, and hence pr is draft lol)

Regarding recipes:

  1. Recipes can be found in the Storybook folder "Recipes", for example, which are at the bottom due to alphabetic organization amongst folders (components, pages, recipes).
  2. Recipes are not exported and are code examples to mimic / copypasta

/* Firefox */
.slider__input::-moz-range-track {
/* fills left side of track as a percentage of the input value */
background: linear-gradient(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would be nice to use accent-color but to my knowledge only colors the filled track and is too modern for our browser policy

@jeremiah-clothier
Copy link
Contributor

fyi @allisonbrownczi I think this PR is relevant for https://czi-tech.atlassian.net/browse/GST-108?

@allisonbrownczi
Copy link
Contributor

@jinlee93 I can't seem to respond to specific comments, this is in regards to the tooltip vs markers.
When using this component would it be possible to add a dataset (ie pass in a list attribute) and have us consumers style it ourselves?

@jinlee93 jinlee93 added the do not merge PRs that should not be merged (even if the build is green or there are approvals) label Feb 21, 2023
@jinlee93
Copy link
Contributor Author

jinlee93 commented Feb 21, 2023

@jinlee93 I can't seem to respond to specific comments, this is in regards to the tooltip vs markers. When using this component would it be possible to add a dataset (ie pass in a list attribute) and have us consumers style it ourselves?

@allisonbrownczi I think in a week or two would have a better answer, but for now:
This current implementation relies on native <input type="range"> so it does allow for using list attribute (for now), but some thoughts:

  1. Difficult to style native tick marks that's used with list attribute and have to finagle for each browser
  2. We're moving towards a consistent design for what the marker labels would look like, feel free to list any concerns here or in the mocks
  3. We're thinking consumers would add the marker labels separate from the slider component
  4. <datalist> options don't display by default for Safari, would have to figure that out
    Screenshot 2023-02-21 at 12 21 45 PM

I can pair / discuss / feel free to bring up any thoughts / comments
@booc0mtaco @anniehu4

@jinlee93 jinlee93 removed the do not merge PRs that should not be merged (even if the build is green or there are approvals) label Feb 24, 2023
@jinlee93 jinlee93 marked this pull request as ready for review February 24, 2023 00:12
)}
</div>
);
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Considered subcomponentizing the input and the markers but both need the css properties of the whole component to be styled properly

.slider__input {
/* increases vertical hitbox for target size accessibility */
padding-top: 22px;
padding-bottom: 22px;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Increased slider hitbox

/**
* List of markers to imply slider value.
*/
markers?: string[];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently constrained to strings (which could also incl emoji, for better or for worse) and evenly separates them out via justify-content: space-between. Could consider expanding to ReactNode if the need arises

Copy link
Contributor

Choose a reason for hiding this comment

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

I think strings should be enough for our usecase at least

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 a little too open-ended at the moment. I think the intent is that the markers will be determined by the min/max/step values, and the number of markers is mathematically determined by those three props when the display some arbitrary text (max - min / step).

One way to account for this is to add checks to make sure markers is properly constrained. That would be useful whether they are simple strings or other symbols, or ReactNode[]. spec tests to verify it works too.

Also, if the common case is that markers are always these numeric sets, we can add a marker utility function to generate the steps. For example, markers is either string[] or 'numeric' (a special documented keyword), where we check for numeric and generate the array values for consumers.

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 a little too open-ended at the moment. I think the intent is that the markers will be determined by the min/max/step values, and the number of markers is mathematically determined by those three props when the display some arbitrary text (max - min / step).

I like this idea

Another idea is that we could update markers to be customizable markers?: Array<{ label?: string, value: number }> instead of markers: Array<string>, but then placing the actual markers may be get complicated

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea placing the markers according to the value of the marker (like how <option> is done) does complicate things and make the prop a little more complex than I want. However, if the need arises for it in a product, will definitely be considered.

Andrew and I did pair on the generation of the markers which is now in this pr

@jinlee93
Copy link
Contributor Author

@allisonbrownczi Added markers prop (string[]) for markers. Didn't go with <datalist> b/c Safari really does not want to display that element... Should be fine since screen readers rely on value of the input and the markers are for visual purposes only. It does lose the autocomplete(snapping) feature of using list attribute but I think it's worth losing for Safari
cc @booc0mtaco

Copy link
Contributor

@booc0mtaco booc0mtaco left a comment

Choose a reason for hiding this comment

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

We should also test what happens when the number of markers doesn't align with the min/max/step values. Unlike using <datalist> markers will only allow evenly-spaced marks

src/components/Slider/Slider.tsx Outdated Show resolved Hide resolved
src/components/Slider/Slider.tsx Outdated Show resolved Hide resolved
src/components/Slider/Slider.tsx Outdated Show resolved Hide resolved
src/components/Slider/Slider.tsx Outdated Show resolved Hide resolved
/**
* List of markers to imply slider value.
*/
markers?: string[];
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 a little too open-ended at the moment. I think the intent is that the markers will be determined by the min/max/step values, and the number of markers is mathematically determined by those three props when the display some arbitrary text (max - min / step).

One way to account for this is to add checks to make sure markers is properly constrained. That would be useful whether they are simple strings or other symbols, or ReactNode[]. spec tests to verify it works too.

Also, if the common case is that markers are always these numeric sets, we can add a marker utility function to generate the steps. For example, markers is either string[] or 'numeric' (a special documented keyword), where we check for numeric and generate the array values for consumers.

@jinlee93
Copy link
Contributor Author

jinlee93 commented Feb 24, 2023

We should also test what happens when the number of markers doesn't align with the min/max/step values. Unlike using markers will only allow evenly-spaced marks

the intention right now is to only have evenly-spaced marks

This is a little too open-ended at the moment. I think the intent is that the markers will be determined by the min/max/step values, and the number of markers is mathematically determined by those three props when the display some arbitrary text (max - min / step).

One way to account for this is to add checks to make sure markers is properly constrained. That would be useful whether they are simple strings or other symbols, or ReactNode[]. spec tests to verify it works too.

Also, if the common case is that markers are always these numeric sets, we can add a marker utility function to generate the steps. For example, markers is either string[] or 'numeric' (a special documented keyword), where we check for numeric and generate the array values for consumers.

I left it open like this since steps can be smaller than the differences between markers. E.g. a 0-100 control with steps of 1 but using markers to hint at current value
@booc0mtaco

Copy link
Contributor

@jeremiah-clothier jeremiah-clothier left a comment

Choose a reason for hiding this comment

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

LGTM

src/components/Slider/Slider.stories.tsx Outdated Show resolved Hide resolved
src/components/Slider/Slider.tsx Outdated Show resolved Hide resolved
Comment on lines 11 to 28
/**
* Returns the lowest multiple of 10 that multiplies with all numbers in a list to make them integers.
* Useful for floating point math.
* @example
* lowestTenMultiplier([-2.212, 0.1, 2.0, 100, 1000.01])
* // returns 1000
* @param numbers
* @returns {number} Lowest multiple of 10.
*/
const lowestTenMultiplier = (numbers: number[]): number => {
let multiplier = 1;
while (
numbers.some((number) => !Number.isInteger((number * multiplier) % 1))
) {
multiplier *= 10;
}
return multiplier;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: we can also save this to src/utils and write separate unit tests for it, since this is a generic piece of functionality. The tests for this would serve as a good set of examples too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved to src/util/ and wrote some tests 👍

{label && (
<Label className={labelClassName} htmlFor={sliderId} text={label} />
)}
<input
Copy link
Contributor

Choose a reason for hiding this comment

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

a11y: we might be able to add a meaningful aria-describedby which formats a string describing the slider. This could be a fieldNode (which other inputs use), perhaps hidden by default or something. the fieldNote would override this when specified.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a fieldNote prop that takes the markers space if present. I don't think the markers itself should describe the slider as the native markers don't:
Screenshot 2023-02-27 at 3 53 30 PM

import { findLowestTenMultiplier } from './findLowestTenMultiplier';

describe('findLowestTenMultiplier', () => {
describe('error throws', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

++

@jinlee93 jinlee93 merged commit e7ced34 into next Feb 28, 2023
@jinlee93 jinlee93 deleted the jlee/slider branch February 28, 2023 19:52
@booc0mtaco booc0mtaco mentioned this pull request Mar 2, 2023
booc0mtaco added a commit that referenced this pull request Mar 2, 2023
## [10.0.0](v9.1.0...v10.0.0) (2023-03-02)


### ⚠ BREAKING CHANGES

* remove data-bootstrap-override EDS-850

### Features

* **Avatar:** add avatar component ([#1510](#1510)) ([bc21f85](bc21f85))
* **slider:** create slider ([#1503](#1503)) ([e7ced34](e7ced34))
* **TextareaField:** add TextArea base component and TextareaField ([#1493](#1493)) ([f2ba31d](f2ba31d))


### Bug Fixes

* remove data-bootstrap-override EDS-850 ([3b5d59b](3b5d59b))
* remove old, outdated, unnecessary snapshot ([fb63a34](fb63a34))
* **Select:** sync label design with form fields ([#1506](#1506)) ([efe9330](efe9330))
* update deps ([9a80e7f](9a80e7f))
* upgrade axe-core from 4.4.3 to 4.6.3 ([af3f9c5](af3f9c5))
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.

4 participants