stylex icon indicating copy to clipboard operation
stylex copied to clipboard

Focus for input type range

Open luangjokaj opened this issue 3 months ago • 1 comments

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:

  • ::-webkit-slider-runnable-track
  • ::-moz-range-track
  • ::-webkit-slider-thumb
  • ::-moz-range-thumb

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>
	);
}

luangjokaj avatar Mar 07 '24 17:03 luangjokaj