From 93968a7d0e5013a21580415fcde2d8e85b37aa65 Mon Sep 17 00:00:00 2001 From: lerte smith Date: Tue, 15 Oct 2024 22:35:03 +0800 Subject: [PATCH] chore: Slider --- apps/docs/src/usages/sliders.tsx | 5 +- .../actify/src/components/Sliders/Slider.tsx | 101 +++---- .../actify/src/components/Sliders/Thumb.tsx | 45 +++- .../src/components/Sliders/slider.module.css | 255 +++++++++--------- 4 files changed, 194 insertions(+), 212 deletions(-) diff --git a/apps/docs/src/usages/sliders.tsx b/apps/docs/src/usages/sliders.tsx index b42587c..be34fa8 100644 --- a/apps/docs/src/usages/sliders.tsx +++ b/apps/docs/src/usages/sliders.tsx @@ -2,6 +2,9 @@ import { Slider } from 'actify' export default () => { return ( - + <> + + + ) } diff --git a/packages/actify/src/components/Sliders/Slider.tsx b/packages/actify/src/components/Sliders/Slider.tsx index 27982b2..ea0f4f7 100644 --- a/packages/actify/src/components/Sliders/Slider.tsx +++ b/packages/actify/src/components/Sliders/Slider.tsx @@ -1,11 +1,6 @@ -'use client' - import { AriaSliderProps, useNumberFormatter, useSlider } from 'react-aria' -import React, { useId } from 'react' -import { Elevation } from './../Elevation' -import { FocusRing } from '../FocusRing' -import { Ripple } from './../Ripple' +import React from 'react' import { Thumb } from './Thumb' import clsx from 'clsx' import styles from './slider.module.css' @@ -19,21 +14,12 @@ type SliderProps = { } & AriaSliderProps const Slider = (props: SliderProps) => { - const { - id, - labeled, - className, - minValue = 0, - maxValue = 100, - isDisabled - } = props + const { labeled, minValue = 0, maxValue = 100 } = props - const sliderId = id || `actify-slider${useId()}` const trackRef = React.useRef(null) const numberFormatter = useNumberFormatter(props.formatOptions) const state = useSliderState({ ...props, numberFormatter }) - - const { groupProps, trackProps, outputProps } = useSlider( + const { groupProps, trackProps, labelProps, outputProps } = useSlider( props, state, trackRef @@ -45,64 +31,41 @@ const Slider = (props: SliderProps) => { return (
+ {/* Create a container for the label and output element. */} + {props.label && ( +
+ + {state.getThumbValueLabel(0)} +
+ )} + {/* The track element holds the visible track line and the thumb. */}
-
- -
-
-
-
-
-
- - - -
- -
- {/* labeled */} - {labeled && ( - - - {state.getThumbValueLabel(0)} - - - )} -
-
-
-
+
) } -Slider.displayName = 'Actify.Slider' - export { Slider } diff --git a/packages/actify/src/components/Sliders/Thumb.tsx b/packages/actify/src/components/Sliders/Thumb.tsx index 362c451..f500010 100644 --- a/packages/actify/src/components/Sliders/Thumb.tsx +++ b/packages/actify/src/components/Sliders/Thumb.tsx @@ -1,22 +1,28 @@ import { AriaSliderThumbProps, + VisuallyHidden, mergeProps, useFocusRing, useSliderThumb } from 'react-aria' +import { Elevation } from '../Elevation/Elevation' +import { FocusRing } from '../FocusRing' import React from 'react' +import { Ripple } from '../Ripple/Ripple' import { SliderState } from 'react-stately' import clsx from 'clsx' import styles from './slider.module.css' type ThumbProps = { + labeled?: boolean state: SliderState + outputProps: React.OutputHTMLAttributes trackRef: React.RefObject } & AriaSliderThumbProps -const Thumb = (props: ThumbProps) => { - const { state, trackRef, index, name } = props +export function Thumb(props: ThumbProps) { + const { labeled, outputProps, state, trackRef, index, name } = props const inputRef = React.useRef(null) const { thumbProps, inputProps, isDragging } = useSliderThumb( { @@ -28,20 +34,37 @@ const Thumb = (props: ThumbProps) => { state ) - const { focusProps, isFocusVisible } = useFocusRing() + const { focusProps, isFocused, isFocusVisible } = useFocusRing() return (
- +
+ + + + {isFocusVisible && } + + + {/* labeled */} + {labeled && ( + + {state.getThumbValueLabel(0)} + + )} +
) } - -export { Thumb } diff --git a/packages/actify/src/components/Sliders/slider.module.css b/packages/actify/src/components/Sliders/slider.module.css index 416e369..d0cdfe1 100644 --- a/packages/actify/src/components/Sliders/slider.module.css +++ b/packages/actify/src/components/Sliders/slider.module.css @@ -1,4 +1,5 @@ .slider { + display: flex; --_active-track-color: var( --md-slider-active-track-color, rgb(var(--md-sys-color-primary, 103 80 164)) @@ -147,13 +148,135 @@ --md-elevation-shadow-color: var(--_handle-shadow-color); } +.slider.horizontal { + flex-direction: column; + width: 100%; +} + +.slider.vertical { + height: 150px; +} + +.label-container { + display: flex; + justify-content: space-between; +} +.vertical .label-container { + flex-direction: column; +} + +.slider.horizontal .track { + height: 40px; + width: 100%; +} + +/* track full */ +.track:before { + content: attr(x); + display: block; + position: absolute; + background: var(--_inactive-track-color); + border-radius: var(--_active-track-shape); +} +/* track current */ +.track:after { + content: attr(x); + display: block; + position: absolute; + background: var(--_active-track-color); + border-radius: var(--_active-track-shape); + clip-path: inset( + 0 + calc( + var(--_with-tick-marks-container-size) * + min((1 - var(--_end-fraction)) * 1000000000, 1) + + (100% - var(--_with-tick-marks-container-size) * 2) * + (1 - var(--_end-fraction)) + ) + 0 + calc( + var(--_with-tick-marks-container-size) * + min(var(--_start-fraction) * 1000000000, 1) + + (100% - var(--_with-tick-marks-container-size) * 2) * + var(--_start-fraction) + ) + ); +} +.vertical .track:after { + clip-path: inset( + calc( + var(--_with-tick-marks-container-size) * + min((1 - var(--_end-fraction)) * 1000000000, 1) + + (100% - var(--_with-tick-marks-container-size) * 2) * + (1 - var(--_end-fraction)) + ) + 0 + calc( + var(--_with-tick-marks-container-size) * + min(var(--_start-fraction) * 1000000000, 1) + + (100% - var(--_with-tick-marks-container-size) * 2) * + var(--_start-fraction) + ) + 0 + ); +} + +.slider.horizontal .track:before, +.slider.horizontal .track:after { + height: var(--_active-track-height); + width: 100%; + top: 50%; + transform: translateY(-50%); +} + +.slider.vertical .track { + width: 30px; + height: 100%; +} + +.slider.vertical .track:before, +.slider.vertical .track:after { + width: var(--_active-track-height); + height: 100%; + left: 50%; + transform: translateX(-50%); +} + +.handle { + z-index: 1; + block-size: var(--_state-layer-size); + inline-size: var(--_state-layer-size); + display: flex; + place-content: center; + place-items: center; +} +.thumb { + width: var(--_handle-width); + height: var(--_handle-height); + background: var(--_handle-color); + border-radius: var(--_handle-shape); +} + +.slider.horizontal .handle { + top: 50%; +} + +.slider.vertical .handle { + left: 50%; +} + +.track.disabled { + opacity: 0.4; +} + .slider .label { position: absolute; box-sizing: border-box; display: flex; padding: 4px; - place-content: center; place-items: center; + place-content: center; + left: 50%; border-radius: var(--md-sys-shape-corner-full, 9999px); color: var(--_label-text-color); font-family: var(--_label-text-font); @@ -166,7 +289,6 @@ background: var(--_label-container-color); transition: transform 100ms cubic-bezier(0.2, 0, 0, 1) 0s; transform-origin: center bottom; - transform: scale(0); &:before { inline-size: calc(var(--_label-container-height) / 2); block-size: calc(var(--_label-container-height) / 2); @@ -186,132 +308,3 @@ background: inherit; } } - -.slider .container { - flex: 1 1 0%; - display: flex; - align-items: center; - position: relative; - block-size: var(--_state-layer-size); - pointer-events: none; - touch-action: none; - &:has(input:hover) .label, - &:has(input:active) .label, - &:has(input:focus-within) .label { - transform: scale(1); - } -} -.slider .input { - opacity: 0; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - position: absolute; - box-sizing: border-box; - height: 100%; - width: 100%; - margin: 0px; - background: rgba(0, 0, 0, 0); - cursor: pointer; - pointer-events: auto; - appearance: none; -} - -.track { - position: absolute; - inset: 0px; - display: flex; - align-items: center; - &:before { - background-color: var(--_inactive-track-color); - block-size: var(--_inactive-track-height); - border-radius: var(--_inactive-track-shape); - position: absolute; - content: ''; - inset-inline-start: calc( - var(--_state-layer-size) / 2 - var(--_with-tick-marks-container-size) - ); - inset-inline-end: calc( - var(--_state-layer-size) / 2 - var(--_with-tick-marks-container-size) - ); - background-size: calc( - (100% - var(--_with-tick-marks-container-size) * 2) / var(--_tick-count) - ) - 100%; - } - &:after { - background: var(--_active-track-color); - block-size: var(--_active-track-height); - border-radius: var(--_active-track-shape); - clip-path: inset( - 0 - calc( - var(--_with-tick-marks-container-size) * - min((1 - var(--_end-fraction)) * 1000000000, 1) + - (100% - var(--_with-tick-marks-container-size) * 2) * - (1 - var(--_end-fraction)) - ) - 0 - calc( - var(--_with-tick-marks-container-size) * - min(var(--_start-fraction) * 1000000000, 1) + - (100% - var(--_with-tick-marks-container-size) * 2) * - var(--_start-fraction) - ) - ); - position: absolute; - content: ''; - inset-inline-start: calc( - var(--_state-layer-size) / 2 - var(--_with-tick-marks-container-size) - ); - inset-inline-end: calc( - var(--_state-layer-size) / 2 - var(--_with-tick-marks-container-size) - ); - background-size: calc( - (100% - var(--_with-tick-marks-container-size) * 2) / var(--_tick-count) - ) - 100%; - } -} -.slider .handle-container-padded { - position: relative; - block-size: 100%; - inline-size: 100%; - padding-inline: calc(var(--_state-layer-size) / 2); -} - -.slider .handle-container-block { - position: relative; - block-size: 100%; - inline-size: 100%; -} - -.slider .handle-container { - position: absolute; - inset-block: 0px; - inset-inline-start: calc(100% * var(--_start-fraction)); - inline-size: calc(100% * (var(--_end-fraction) - var(--_start-fraction))); - &.hover .label { - transform: scale(1); - } -} - -.slider .handle { - --md-ripple-hover-color: var(--_hover-state-layer-color); - --md-ripple-hover-opacity: var(--_hover-state-layer-opacity); - --md-ripple-pressed-color: var(--_pressed-state-layer-color); - --md-ripple-pressed-opacity: var(--_pressed-state-layer-opacity); - position: absolute; - block-size: var(--_state-layer-size); - inline-size: var(--_state-layer-size); - border-radius: var(--_handle-shape); - display: flex; - place-content: center; - place-items: center; -} - -.slider .handle-nub { - position: absolute; - height: var(--_handle-height); - width: var(--_handle-width); - border-radius: var(--_handle-shape); - background: var(--_handle-color); -}