facebook / stylex

StyleX is the styling system for ambitious user interfaces.
https://stylexjs.com
MIT License
8.34k stars 307 forks source link

Focus for input type range #480

Open luangjokaj opened 6 months ago

luangjokaj commented 6 months ago

Describe the issue

I have been playing around with Stylex lately and I absolutely love it. I am already re-writing our design system based on this technology. So first thing first congrats on that πŸ’―

I am currently styling input ranges (<input type="range" />). I can style the :hover and :active states without any problem, but for some reason :focus doesn't behave the same way.

As we know input ranges are a bit more complex to be styled, as we need to target also pseudo-elements to make it look consistent across browsers. In this case i am using:

Expected behavior

The expected behaviour is that :focus should work just the same way as as :hover, or `:active.

Steps to reproduce

":hover": {
    "::-webkit-slider-runnable-track": {
        border: `solid 2px ${colors.secondary}`, // βœ… this works
    },
},
":focus": {
    border: "solid 2px red",  // βœ… this works
    "::-webkit-slider-runnable-track": {
        border: `solid 2px ${colors.secondary}`, // ❌ doesn't work
        boxShadow: `0 0 0 4px ${colors.secondaryLight}`, // ❌ doesn't work
    },
},
":active": {
    "::-webkit-slider-runnable-track": {
        border: `solid 2px ${colors.secondary}`, // βœ… this works
        boxShadow: `0 0 0 2px ${colors.secondaryLight}`, // βœ… this works
    },
},

Test case

No response

Additional comments

Here is the entire components with styles included:

"use client";
import React from "react";
import * as stylex from "@stylexjs/stylex";
import { genericStyles } from "./generic";
import { colors } from "../../tokens.stylex";

interface InputProps
    extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> {
    className?: string;
    label?: string;
    size?: "default" | "big";
    error?: boolean;
    success?: boolean;
    fullWidth?: boolean;
}

const styles = stylex.create({
    range: {
        padding: 0,
        height: 10,
        fontSize: 0,
        boxShadow: "none",
        border: "none",
        background: "transparent",
        "::-webkit-slider-runnable-track": {
            width: "100%",
            cursor: "pointer",
            borderRadius: 25,
            border: `solid 2px ${colors.grayLight}`,
            transition: "all 0.3s ease",
            boxShadow: `0 0 0 0 ${colors.secondaryLight}`,
        },
        "::-webkit-slider-thumb": {
            appearance: "none",
            padding: 0,
            margin: 0,
            cursor: "pointer",
            background: colors.secondary,
            border: "0 solid transparent",
            borderRadius: "50%",
            transition: "all 0.3s ease",
        },
        "::-moz-range-track": {
            width: "100%",
            cursor: "pointer",
            borderRadius: 25,
            border: `solid 2px ${colors.grayLight}`,
            transition: "all 0.3s ease",
            boxShadow: `0 0 0 0 ${colors.secondaryLight}`,
        },
        "::-moz-range-thumb": {
            appearance: "none",
            padding: 0,
            margin: 0,
            cursor: "pointer",
            background: colors.secondary,
            border: "0 solid transparent",
            borderRadius: "50%",
            transition: "all 0.3s ease",
        },
    },
    default: {
        minWidth: 130,
        height: 32,
        "::-webkit-slider-runnable-track": {
            height: 10,
        },
        "::-webkit-slider-thumb": {
            width: 22,
            height: 22,
            marginTop: -8,
        },
        "::-moz-range-track": {
            height: 6,
        },
        "::-moz-range-thumb": {
            width: 22,
            height: 22,
        },
    },
    big: {
        minWidth: 200,
        height: 32,
        "::-webkit-slider-runnable-track": {
            height: 14,
        },
        "::-webkit-slider-thumb": {
            width: 32,
            height: 32,
            marginTop: -11,
        },
        "::-moz-range-track": {
            height: 10,
        },
        "::-moz-range-thumb": {
            width: 32,
            height: 32,
        },
    },
    actions: {
        ":hover": {
            "::-webkit-slider-runnable-track": {
                border: `solid 2px ${colors.secondary}`,
            },
            "::-moz-range-track": {
                border: `solid 2px ${colors.secondary}`,
            },
        },
        ":focus": {
            "::-webkit-slider-runnable-track": {
                border: `solid 2px ${colors.secondary}`,
                boxShadow: `0 0 0 4px ${colors.secondaryLight}`,
            },
            "::-webkit-slider-thumb": {
                boxShadow: `0 0 0 4px ${colors.secondaryLight}`,
            },
            "::-moz-range-track": {
                border: `solid 2px ${colors.secondary}`,
                boxShadow: `0 0 0 4px ${colors.secondaryLight}`,
            },
            "::-moz-range-thumb": {
                boxShadow: `0 0 0 4px ${colors.secondaryLight}`,
            },
        },
        ":active": {
            "::-webkit-slider-runnable-track": {
                border: `solid 2px ${colors.secondary}`,
                boxShadow: `0 0 0 2px ${colors.secondaryLight}`,
            },
            "::-webkit-slider-thumb": {
                boxShadow: `0 0 0 2px ${colors.secondaryLight}`,
            },
            "::-moz-range-track": {
                border: `solid 2px ${colors.secondary}`,
                boxShadow: `0 0 0 2px ${colors.secondaryLight}`,
            },
            "::-moz-range-thumb": {
                boxShadow: `0 0 0 2px ${colors.secondaryLight}`,
            },
        },
    },
    disabled: {
        cursor: "not-allowed",
        "::-webkit-slider-runnable-track": {
            background: colors.grayLight,
            cursor: "not-allowed",
        },
        "::-webkit-slider-thumb": {
            background: colors.gray,
            cursor: "not-allowed",
        },
        "::-moz-range-track": {
            background: colors.grayLight,
            cursor: "not-allowed",
        },
        "::-moz-range-thumb": {
            background: colors.gray,
            cursor: "not-allowed",
        },
    },
    fullWidth: {
        width: "100%",
    },
});

export default function RangeSlider({
    className,
    label,
    size = "default",
    error,
    success,
    fullWidth,
    disabled,
    ...props
}: InputProps) {
    return (
        <span {...stylex.props(fullWidth && styles.fullWidth)}>
            <input
                className={className}
                type="range"
                {...stylex.props(
                    genericStyles.resetButton,
                    styles.range,
                    !disabled && styles.actions,
                    styles[size],
                    fullWidth && styles.fullWidth,
                    disabled && styles.disabled,
                )}
                {...props}
                disabled={disabled}
            />
            {label && <label htmlFor={props.id}>{label}</label>}
        </span>
    );
}
nmn commented 6 months ago

What you're trying to achieve is essentially a descendent selector which isn't officially supported. This kind of case is what is what we are still trying to solve.

You will need to use CSS variables as a workaround for this.