From b2d971c08ebf0e9e7243458e67a12a57444a6d1e Mon Sep 17 00:00:00 2001 From: Theophile Sandoz Date: Thu, 20 Jul 2023 17:55:26 +0200 Subject: [PATCH 1/6] Add the RatioSlider component --- .../_inputs/Slider/RatioSlider.styles.ts | 87 +++++++++++++++++++ .../components/_inputs/Slider/RatioSlider.tsx | 78 +++++++++++++++++ .../src/components/_inputs/Slider/index.ts | 1 + 3 files changed, 166 insertions(+) create mode 100644 packages/atlas/src/components/_inputs/Slider/RatioSlider.styles.ts create mode 100644 packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx diff --git a/packages/atlas/src/components/_inputs/Slider/RatioSlider.styles.ts b/packages/atlas/src/components/_inputs/Slider/RatioSlider.styles.ts new file mode 100644 index 0000000000..3613b4d8e8 --- /dev/null +++ b/packages/atlas/src/components/_inputs/Slider/RatioSlider.styles.ts @@ -0,0 +1,87 @@ +import styled from '@emotion/styled' + +import { cVar, sizes } from '@/styles' + +export const Wrapper = styled.div` + position: relative; + width: 100%; +` + +export const Track = styled.svg` + pointer-events: none; + overflow: visible; + height: ${sizes(6)}; + width: calc(100% - ${sizes(5)}); + margin: 0 ${sizes(5 / 2)} ${sizes(6)}; + + .rail { + stroke-width: ${sizes(1 / 2)}; + stroke: ${cVar('colorCoreNeutral600')}; + + &-left { + stroke: ${cVar('colorBackgroundPrimary')}; + } + + .steps .step--active { + stroke: ${cVar('colorBackgroundPrimary')}; + } + } + + .knob { + paint-order: stroke; + stroke-width: 0; + stroke: ${cVar('colorBackgroundPrimary')}; + fill: ${cVar('colorBackgroundPrimary')}; + } + + .cutout-mask { + paint-order: stroke; + stroke-width: 0; + stroke: #000; + fill: #000; + } + + text { + font-size: ${sizes(3)}; + user-select: none; + + &:nth-of-type(1) { + fill: ${cVar('colorTextMuted')}; + text-anchor: start; + } + + &:nth-of-type(2) { + fill: ${cVar('colorTextStrong')}; + text-anchor: middle; + } + + &:nth-of-type(3) { + fill: ${cVar('colorTextMuted')}; + text-anchor: end; + } + } +` + +export const Range = styled.input` + position: absolute; + width: 100%; + opacity: 0; + height: ${sizes(8)}; + cursor: grab; + + &:active { + cursor: grabbing; + + & + svg .knob { + fill: ${cVar('colorTextStrong')}; + } + } + + &:active, + &:hover { + & + svg .knob, + & + svg .cutout-mask { + stroke-width: ${sizes(2)}; + } + } +` diff --git a/packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx b/packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx new file mode 100644 index 0000000000..4d5b74b467 --- /dev/null +++ b/packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx @@ -0,0 +1,78 @@ +import { ChangeEventHandler, forwardRef, useMemo, useState } from 'react' + +import { sizes } from '@/styles' + +import { Range, Track, Wrapper } from './RatioSlider.styles' + +type Props = { + min?: number + max?: number + step?: number + defaultValue?: number + value?: number + onChange?: (value: number) => void +} + +export const RatioSlider = forwardRef( + ({ min = 0, max = 100, step = 10, defaultValue = 50, value: controlledValue, onChange }, ref) => { + const [internalValue, setInternalValue] = useState(defaultValue) + const value = controlledValue ?? internalValue + + const handleChange: ChangeEventHandler = useMemo( + () => + typeof controlledValue === 'undefined' + ? (evt) => setInternalValue(Number(evt.target.value)) + : (evt) => onChange?.(Number(evt.target.value)), + [controlledValue, onChange] + ) + + const length = max - min + const valuePercent = `${(value / length) * 100}%` + + const steps = useMemo(() => { + const stepPercent = (step / length) * 100 + return Array.from({ length: Math.ceil(length / step) + 1 }).map( + (_, index) => `${Math.min(index * stepPercent, 100)}%` + ) + }, [step, length]) + + return ( + + + + + + + + + + + + + + + + + {steps.map((x, index) => { + const cls = `step step${Math.min(index * step, max) <= internalValue ? '--active' : ''}` + return + })} + + + + + {min}% + + + {internalValue}% + + + {max}% + + + + ) + } +) + +RatioSlider.displayName = 'RatioSlider' diff --git a/packages/atlas/src/components/_inputs/Slider/index.ts b/packages/atlas/src/components/_inputs/Slider/index.ts index 4b76ec723b..7229fcad58 100644 --- a/packages/atlas/src/components/_inputs/Slider/index.ts +++ b/packages/atlas/src/components/_inputs/Slider/index.ts @@ -1 +1,2 @@ export * from './Slider' +export * from './RatioSlider' From bb2ce5ce385ac89f2647bcd72563f1204595c315 Mon Sep 17 00:00:00 2001 From: Theophile Sandoz Date: Thu, 20 Jul 2023 18:35:29 +0200 Subject: [PATCH 2/6] Add a story --- .../_inputs/Slider/RadioSlider.stories.tsx | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 packages/atlas/src/components/_inputs/Slider/RadioSlider.stories.tsx diff --git a/packages/atlas/src/components/_inputs/Slider/RadioSlider.stories.tsx b/packages/atlas/src/components/_inputs/Slider/RadioSlider.stories.tsx new file mode 100644 index 0000000000..83f942367d --- /dev/null +++ b/packages/atlas/src/components/_inputs/Slider/RadioSlider.stories.tsx @@ -0,0 +1,26 @@ +import { Meta, StoryObj } from '@storybook/react' + +import { RatioSlider as RatioSlider_ } from './RatioSlider' + +export default { + title: 'inputs/Slider/RatioSlider', + component: RatioSlider_, + args: { + min: 0, + max: 100, + step: 10, + defaultValue: 50, + }, + argTypes: { + value: { table: { disable: true } }, + }, +} as Meta + +type Args = { + min: number + max: number + step?: number + disabled?: boolean +} + +export const RatioSlider: StoryObj = {} From 6b5175f1b7df6a7d75e176a7023d06d8159e4816 Mon Sep 17 00:00:00 2001 From: Theophile Sandoz Date: Thu, 20 Jul 2023 18:36:18 +0200 Subject: [PATCH 3/6] Annimate the transitions --- .../src/components/_inputs/Slider/RatioSlider.styles.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/atlas/src/components/_inputs/Slider/RatioSlider.styles.ts b/packages/atlas/src/components/_inputs/Slider/RatioSlider.styles.ts index 3613b4d8e8..67335f10b5 100644 --- a/packages/atlas/src/components/_inputs/Slider/RatioSlider.styles.ts +++ b/packages/atlas/src/components/_inputs/Slider/RatioSlider.styles.ts @@ -7,6 +7,8 @@ export const Wrapper = styled.div` width: 100%; ` +const TRANSITION_DURATION = '100ms' + export const Track = styled.svg` pointer-events: none; overflow: visible; @@ -17,6 +19,7 @@ export const Track = styled.svg` .rail { stroke-width: ${sizes(1 / 2)}; stroke: ${cVar('colorCoreNeutral600')}; + transition: all ${TRANSITION_DURATION}; &-left { stroke: ${cVar('colorBackgroundPrimary')}; @@ -32,6 +35,7 @@ export const Track = styled.svg` stroke-width: 0; stroke: ${cVar('colorBackgroundPrimary')}; fill: ${cVar('colorBackgroundPrimary')}; + transition: all ${TRANSITION_DURATION}; } .cutout-mask { @@ -39,6 +43,7 @@ export const Track = styled.svg` stroke-width: 0; stroke: #000; fill: #000; + transition: all ${TRANSITION_DURATION}; } text { From aae4a1be5368d0a79dc9cd543b4afb91d73a82d6 Mon Sep 17 00:00:00 2001 From: Theophile Sandoz Date: Thu, 20 Jul 2023 18:45:40 +0200 Subject: [PATCH 4/6] Rename the svg mask --- packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx b/packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx index 4d5b74b467..6132dc7280 100644 --- a/packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx +++ b/packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx @@ -43,12 +43,12 @@ export const RatioSlider = forwardRef( - + - + From d3afcf1aa3956e7d8e18c9815067e170c2b7359f Mon Sep 17 00:00:00 2001 From: Theophile Sandoz Date: Fri, 21 Jul 2023 10:05:10 +0200 Subject: [PATCH 5/6] Fix the RatioSliderProps type name --- packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx b/packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx index 6132dc7280..bd84640b9d 100644 --- a/packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx +++ b/packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx @@ -4,7 +4,7 @@ import { sizes } from '@/styles' import { Range, Track, Wrapper } from './RatioSlider.styles' -type Props = { +type RatioSliderProps = { min?: number max?: number step?: number @@ -13,7 +13,7 @@ type Props = { onChange?: (value: number) => void } -export const RatioSlider = forwardRef( +export const RatioSlider = forwardRef( ({ min = 0, max = 100, step = 10, defaultValue = 50, value: controlledValue, onChange }, ref) => { const [internalValue, setInternalValue] = useState(defaultValue) const value = controlledValue ?? internalValue From 58e4812647e31d224b63a2f528483df0f4b129eb Mon Sep 17 00:00:00 2001 From: Theophile Sandoz Date: Fri, 21 Jul 2023 10:09:32 +0200 Subject: [PATCH 6/6] Fix the active steps class name --- .../atlas/src/components/_inputs/Slider/RatioSlider.styles.ts | 2 +- packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/atlas/src/components/_inputs/Slider/RatioSlider.styles.ts b/packages/atlas/src/components/_inputs/Slider/RatioSlider.styles.ts index 67335f10b5..9cc181b1c8 100644 --- a/packages/atlas/src/components/_inputs/Slider/RatioSlider.styles.ts +++ b/packages/atlas/src/components/_inputs/Slider/RatioSlider.styles.ts @@ -25,7 +25,7 @@ export const Track = styled.svg` stroke: ${cVar('colorBackgroundPrimary')}; } - .steps .step--active { + .steps .active { stroke: ${cVar('colorBackgroundPrimary')}; } } diff --git a/packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx b/packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx index bd84640b9d..227b574cb8 100644 --- a/packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx +++ b/packages/atlas/src/components/_inputs/Slider/RatioSlider.tsx @@ -54,7 +54,7 @@ export const RatioSlider = forwardRef( {steps.map((x, index) => { - const cls = `step step${Math.min(index * step, max) <= internalValue ? '--active' : ''}` + const cls = Math.min(index * step, max) <= internalValue ? 'active' : '' return })}