fbd6df33b4
Adds System Preferences, Dark and Light themes, gated by the `hasThemeSwitcher` feature flag (off by default — dark stays the default theme for everyone). Old theme is now "Classic"and set as default. "System preferences" theme has both Light and Dark modes and uses your laptop settings to use a correct one. It has less color accents (specifically less colored text), and they are the same for both modes, only grayscale values change between them. And Light/Dark themes can be used separately. New Contrast setting is available for System Preferences, Dark and Light themes - it changes the contrast for the whole app. All new visual Settings live in Account.
131 lines
4.4 KiB
TypeScript
131 lines
4.4 KiB
TypeScript
import * as React from "react";
|
|
import { useImperativeHandle, useRef } from "react";
|
|
import { cn } from "~/utils/cn";
|
|
import { Icon, type RenderIcon } from "./Icon";
|
|
|
|
const containerBase =
|
|
"has-focus-visible:outline-hidden has-focus-visible:ring-1 has-focus-visible:ring-border-bright has-focus-visible:ring-offset-0 has-[:focus]:border-ring has-focus:outline-hidden has-focus:ring-1 has-[:focus]:ring-ring has-disabled:cursor-not-allowed has-disabled:opacity-50 ring-offset-background transition cursor-text";
|
|
|
|
const inputBase =
|
|
"h-full w-full text-text-bright bg-transparent file:border-0 file:bg-transparent file:text-base file:font-medium placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-0 disabled:cursor-not-allowed outline-hidden ring-0 border-none [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:m-0 [&::-webkit-inner-spin-button]:m-0 [&]:[-moz-appearance:textfield]";
|
|
|
|
const variants = {
|
|
large: {
|
|
container:
|
|
"px-1 w-full h-10 rounded-[3px] border border-border-bright/50 shadow-xs bg-input-bg hover:bg-background-raised",
|
|
input: "px-2 text-sm",
|
|
iconSize: "size-4 ml-1",
|
|
accessory: "pr-1",
|
|
},
|
|
medium: {
|
|
container:
|
|
"px-1 h-8 w-full rounded border border-border-bright/50 shadow-xs bg-input-bg hover:bg-background-raised",
|
|
input: "px-1.5 rounded text-sm",
|
|
iconSize: "size-4 ml-0.5",
|
|
accessory: "pr-1",
|
|
},
|
|
small: {
|
|
container:
|
|
"px-1 h-6 w-full rounded border border-border-bright/50 shadow-xs bg-input-bg hover:bg-background-raised",
|
|
input: "px-1 rounded text-xs",
|
|
iconSize: "size-3 ml-0.5",
|
|
accessory: "pr-0.5",
|
|
},
|
|
tertiary: {
|
|
container: "px-1 h-6 w-full rounded hover:bg-background-hover",
|
|
input: "px-1 rounded text-xs",
|
|
iconSize: "size-3 ml-0.5",
|
|
accessory: "pr-0.5",
|
|
},
|
|
"secondary-small": {
|
|
container:
|
|
"px-1 h-6 w-full rounded border border-border-bright/50 shadow-xs bg-input-bg hover:bg-background-raised",
|
|
input: "px-1 rounded text-xs",
|
|
iconSize: "size-3 ml-0.5",
|
|
accessory: "pr-0.5",
|
|
},
|
|
"outline/large": {
|
|
container: "px-1 h-10 w-full rounded border border-grid-bright hover:border-border-brighter",
|
|
input: "px-2 rounded text-sm",
|
|
iconSize: "size-4 ml-1",
|
|
accessory: "pr-1",
|
|
},
|
|
"outline/medium": {
|
|
container: "px-1 h-8 w-full rounded border border-grid-bright hover:border-border-brighter",
|
|
input: "px-1 rounded text-sm",
|
|
iconSize: "size-4 ml-0.5",
|
|
accessory: "pr-1",
|
|
},
|
|
"outline/small": {
|
|
container: "px-1 h-6 w-full rounded border border-grid-bright hover:border-border-brighter",
|
|
input: "px-1 rounded text-xs",
|
|
iconSize: "size-3 ml-0.5",
|
|
accessory: "pr-0.5",
|
|
},
|
|
};
|
|
|
|
export type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
|
|
variant?: keyof typeof variants;
|
|
icon?: RenderIcon;
|
|
iconClassName?: string;
|
|
accessory?: React.ReactNode;
|
|
fullWidth?: boolean;
|
|
containerClassName?: string;
|
|
};
|
|
|
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
(
|
|
{
|
|
className,
|
|
type,
|
|
accessory,
|
|
fullWidth = true,
|
|
variant = "medium",
|
|
icon,
|
|
iconClassName,
|
|
containerClassName,
|
|
...props
|
|
},
|
|
ref
|
|
) => {
|
|
const innerRef = useRef<HTMLInputElement>(null);
|
|
useImperativeHandle(ref, () => innerRef.current as HTMLInputElement);
|
|
|
|
const variantContainerClassName = variants[variant].container;
|
|
const inputClassName = variants[variant].input;
|
|
const variantIconClassName = variants[variant].iconSize;
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"flex items-center",
|
|
containerBase,
|
|
variantContainerClassName,
|
|
containerClassName,
|
|
fullWidth ? "w-full" : "max-w-max"
|
|
)}
|
|
onClick={() => innerRef.current && innerRef.current.focus()}
|
|
>
|
|
{icon && (
|
|
<div className="pointer-events-none flex items-center">
|
|
<Icon
|
|
icon={icon}
|
|
className={cn(variantIconClassName, "text-text-dimmed", iconClassName)}
|
|
/>
|
|
</div>
|
|
)}
|
|
<input
|
|
type={type}
|
|
className={cn("grow", inputBase, inputClassName, className)}
|
|
ref={innerRef}
|
|
{...props}
|
|
/>
|
|
{accessory && <div className={cn(variants[variant].accessory)}>{accessory}</div>}
|
|
</div>
|
|
);
|
|
}
|
|
);
|
|
Input.displayName = "Input";
|
|
|
|
export { Input };
|