Skip to content

Commit

Permalink
feat(Slider): add alignment styles and recipes
Browse files Browse the repository at this point in the history
- small tweaks to the sytling of marker text
- several recipes to support examples in ZeroHeight
- integrate examples with some interactivity and animation
  • Loading branch information
booc0mtaco committed Mar 29, 2023
1 parent d4ddfd5 commit 3903f26
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 1 deletion.
230 changes: 230 additions & 0 deletions .storybook/recipes/Sliders.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import type { StoryObj, Meta } from '@storybook/react';
import React, { useState } from 'react';
import { Slider, InputField, Text, Button, Label } from '../../src';

/**
* React is confused about what `render` is, preventing hooks:
* - `render` gets used in a hidden Story component, which follows the rules
*/
/* eslint-disable react-hooks/rules-of-hooks */

export default {
title: 'Recipes/Sliders',
component: Slider,
decorators: [
(Story) => (
<div className="p-8">
<Story />
</div>
),
],
argTypes: {
fieldNote: {
type: 'string',
},
},
} as Meta<Args>;

type Args = React.ComponentProps<typeof Slider>;

const moodData = [
{ emoji: '😡', value: 0, description: 'Very Upset' },
{ emoji: '🙁', value: 25, description: 'Upset' },
{ emoji: '😐', value: 50, description: 'Neutral' },
{ emoji: '🙂', value: 75, description: 'Happy' },
{ emoji: '😍', value: 100, description: 'Very Happy' },
];

export const UsingInputDisplay: StoryObj<Args> = {
parameters: {
axe: {
disabledRules: ['color-contrast'], // adding for disabled field example
},
},
render: ({ min = 0, max = 100, step = 1, value = 50, ...rest }) => {
const [sliderValue, setSliderValue] = useState(value);

return (
<div className="flex w-full items-end justify-center gap-2">
<Text className="py-3">{min}</Text>
<Slider
className="w-1/2"
label="Slider with input"
max={max}
min={min}
step={step}
{...rest}
onChange={({ target }) => setSliderValue(Number(target.value))}
value={sliderValue}
/>
<Text className="py-3">{max}</Text>
<InputField
aria-label="display value"
className="mx-2 w-14"
readOnly
value={sliderValue}
/>
</div>
);
},
};

export const UsingControlButtons: StoryObj<Args> = {
render: ({ min = 0, max = 100, step = 1, value = 50, ...rest }) => {
const [sliderValue, setSliderValue] = useState(value);

return (
<div className="flex w-full items-end items-center justify-center gap-2">
<Button
aria-label="Decrement"
disabled={sliderValue === min}
onClick={() => setSliderValue(Math.max(min, sliderValue - 1))}
status="neutral"
variant="secondary"
>
&ndash;
</Button>
<Text>{min}</Text>
<Slider
aria-label="select a value"
className="w-1/2"
max={max}
min={min}
step={step}
{...rest}
onChange={({ target }) => setSliderValue(Number(target.value))}
value={sliderValue}
/>
<Text>{max}</Text>
<Button
aria-label="Increment"
disabled={sliderValue === max}
onClick={() => setSliderValue(Math.min(max, sliderValue + 1))}
status="neutral"
variant="secondary"
>
+
</Button>
</div>
);
},
};

export const WithHighlightedContent: StoryObj<Args> = {
render: ({ min = 0, max = 100, step = 25, value = 50, ...rest }) => {
const [sliderValue, setSliderValue] = useState(value);

return (
<article>
<div className="items-top flex justify-center gap-10">
<Slider
aria-describedby="mood-description"
aria-label="Mood Slider"
className="w-1/2"
markers={moodData.map((mood) => mood.description)}
max={max}
min={min}
step={step}
{...rest}
onChange={({ target }) => setSliderValue(Number(target.value))}
value={sliderValue}
/>
<div className="text-h1">
{moodData.map((mood) => {
return sliderValue === mood.value && <>{mood.emoji}</>;
})}
</div>
</div>
<Text className="py-3 text-center" id="mood-description">
Current mood:{' '}
<strong>
{moodData.map((mood) => {
return sliderValue === mood.value && <>{mood.description}</>;
})}
</strong>
</Text>
</article>
);
},
};

export const WithVisualLabel: StoryObj<Args> = {
render: ({ min = 0, max = 100, step = 25, value = 50, ...rest }) => {
const [sliderValue, setSliderValue] = useState(value);

return (
<article>
<div className="items-top flex flex-col justify-center ">
<Label
className="w-1/2 py-4 text-center"
id="slider-label"
text="Mood Slider"
/>
<div className="w-1/2 py-4 text-center text-h1">
{moodData.map((mood) => {
return sliderValue === mood.value && <>{mood.emoji}</>;
})}
</div>
<Slider
aria-label="Mood Slider"
aria-labelledby="slider-label"
className="w-1/2"
markers={moodData.map((mood) => mood.description)}
max={max}
min={min}
step={step}
{...rest}
onChange={({ target }) => setSliderValue(Number(target.value))}
value={sliderValue}
/>
</div>
</article>
);
},
};

export const WithMultipleVisualLabels: StoryObj<Args> = {
render: ({ min = 0, max = 100, step = 25, value = 50, ...rest }) => {
const [sliderValue, setSliderValue] = useState(value);

return (
<article>
<div className="items-top flex flex-col justify-center ">
<Label
className="w-1/2 py-4 text-center"
id="slider-label"
text="Mood Slider"
/>
<div className="flex w-1/2 justify-between py-4 text-center text-h1">
{moodData.map((mood, index) => {
return (
<div
className={
sliderValue === mood.value
? 'scale-150 transition-transform'
: ''
}
key={`mood-${mood.description}`}
>
{mood.emoji}
</div>
);
})}
</div>
<Slider
aria-label="Mood Slider"
aria-labelledby="slider-label"
className="w-1/2"
markers={moodData.map((mood) => mood.description)}
max={max}
min={min}
step={step}
{...rest}
onChange={({ target }) => setSliderValue(Number(target.value))}
value={sliderValue}
/>
</div>
</article>
);
},
};
15 changes: 14 additions & 1 deletion src/components/Slider/Slider.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@
*/
.slider__markers {
display: flex;
align-items: center;
justify-content: space-between;

/* Calculates offset of the markers to align with actual values */
Expand All @@ -166,3 +165,17 @@
.slider__marker--disabled {
color: var(--eds-theme-color-text-disabled);
}

/**
* align the text of the last marker flush with the right edge
*/
.slider__marker:last-child {
text-align: right;
}

/**
* Align all middle markers as centered on the mark point (if multi-line)
*/
.slider__marker:not(:first-child):not(:last-child) {
text-align: center;
}
5 changes: 5 additions & 0 deletions src/components/Slider/Slider.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export default {
</div>
),
],
argTypes: {
fieldNote: {
type: 'string',
},
},
render: (args) => <InteractiveSlider {...args} />,
} as Meta<Args>;

Expand Down

0 comments on commit 3903f26

Please sign in to comment.