|
1 | | -import { ForwardedRef, forwardRef, InputHTMLAttributes, useMemo } from "react"; |
| 1 | +import { ForwardedRef, forwardRef, InputHTMLAttributes, ReactNode, useId, useMemo } from "react"; |
2 | 2 | import { cva, VariantProps as GetVariantProps } from "class-variance-authority"; |
3 | 3 | import { twMerge } from "tailwind-merge"; |
4 | 4 |
|
5 | 5 | import { RequiredKeys } from "@/types/utility"; |
6 | 6 |
|
7 | | -export const style = cva("rounded-full py-2 px-3", { |
| 7 | +import Label, { style as labelStyle } from "./Label"; |
| 8 | + |
| 9 | +export const style = cva("w-full rounded-full py-2 px-3 border-2", { |
8 | 10 | variants: { |
9 | | - variant: { |
10 | | - outlined: "border-2" |
11 | | - }, |
12 | 11 | color: { |
13 | | - primary: "", |
14 | | - secondary: "" |
| 12 | + primary: "border-primary bg-white", |
| 13 | + secondary: "border-secondary bg-white" |
15 | 14 | }, |
16 | 15 | }, |
17 | | - compoundVariants: [ |
18 | | - { |
19 | | - className: "border-primary bg-white", |
20 | | - color: "primary", |
21 | | - variant: "outlined" |
22 | | - }, |
23 | | - { |
24 | | - className: "border-secondary bg-white", |
25 | | - color: "secondary", |
26 | | - variant: "outlined" |
27 | | - } |
28 | | - ], |
29 | 16 | defaultVariants: { |
30 | | - color: "primary", |
31 | | - variant: "outlined" |
| 17 | + color: "primary" |
32 | 18 | } |
33 | 19 | }); |
34 | 20 |
|
| 21 | +const borderStyle = cva("absolute inset-0 border-2 top-[-9px] rounded-[inherit] pl-[var(--label-inset)]", { |
| 22 | + variants: { |
| 23 | + color: { |
| 24 | + primary: "border-primary", |
| 25 | + secondary: "border-secondary" |
| 26 | + }, |
| 27 | + } |
| 28 | +}); |
| 29 | + |
| 30 | +export type BaseProps = { |
| 31 | + inputClassName?: string; |
| 32 | + labelClassName?: string; |
| 33 | + fieldsetClassName?: string; |
| 34 | + labelInset?: string; |
| 35 | + label?: ReactNode; |
| 36 | +}; |
| 37 | + |
35 | 38 | export type TagProps = Omit<InputHTMLAttributes<HTMLInputElement>, "color">; |
36 | 39 | export type VariantProps = GetVariantProps<typeof style>; |
37 | | -export type InputProps = TagProps & RequiredKeys<VariantProps, "color" | "variant">; |
| 40 | +export type InputProps = BaseProps & TagProps & RequiredKeys<VariantProps, "color">; |
38 | 41 |
|
39 | 42 | const Input = forwardRef(( |
40 | 43 | { |
41 | 44 | className, |
| 45 | + inputClassName, |
| 46 | + labelClassName, |
| 47 | + fieldsetClassName, |
| 48 | + labelInset = "1.5rem", |
| 49 | + label, |
42 | 50 | color, |
43 | | - variant, |
| 51 | + id, |
44 | 52 | ...props |
45 | 53 | }: InputProps, |
46 | 54 | ref: ForwardedRef<HTMLInputElement> |
47 | 55 | ) => { |
48 | 56 |
|
| 57 | + const fallbackId = useId(); |
| 58 | + |
49 | 59 | const computedClassName = useMemo( |
50 | | - () => twMerge(style({ color, variant }), className), |
51 | | - [className, color, variant] |
| 60 | + () => twMerge("relative rounded-full", className), |
| 61 | + [className] |
| 62 | + ); |
| 63 | + |
| 64 | + const computedInputClassName = useMemo( |
| 65 | + () => twMerge( |
| 66 | + style({ color }), |
| 67 | + "rounded-[inherit]", |
| 68 | + label ? "border-transparent" : "", |
| 69 | + inputClassName |
| 70 | + ), |
| 71 | + [inputClassName, color, label] |
| 72 | + ); |
| 73 | + |
| 74 | + const computedLabelClassName = useMemo( |
| 75 | + () => twMerge("absolute left-[var(--label-inset)] top-0 translate-x-[2px] -translate-y-1/2 px-1", labelClassName), |
| 76 | + [labelClassName] |
| 77 | + ); |
| 78 | + |
| 79 | + const computedFieldsetClassName = useMemo( |
| 80 | + () => twMerge(borderStyle({ color }), fieldsetClassName), |
| 81 | + [color, fieldsetClassName] |
| 82 | + ); |
| 83 | + |
| 84 | + const computedLegendClassName = useMemo( |
| 85 | + () => twMerge(labelStyle(), "px-1 opacity-0"), |
| 86 | + [computedLabelClassName] |
| 87 | + ); |
| 88 | + |
| 89 | + const insetProps = useMemo<Record<string, string>>( |
| 90 | + () => ({ "--label-inset": labelInset }), |
| 91 | + [labelInset] |
52 | 92 | ); |
53 | 93 |
|
54 | 94 | return ( |
55 | | - <input |
56 | | - ref={ref} |
57 | | - className={computedClassName} |
58 | | - {...props} |
59 | | - /> |
| 95 | + <div className={computedClassName}> |
| 96 | + {label && <Label className={computedLabelClassName} htmlFor={id ?? fallbackId} style={insetProps}> |
| 97 | + {label} |
| 98 | + </Label>} |
| 99 | + {label && <fieldset aria-hidden className={computedFieldsetClassName} style={insetProps}> |
| 100 | + <legend className={computedLegendClassName}>{label}</legend> |
| 101 | + </fieldset>} |
| 102 | + <input |
| 103 | + ref={ref} |
| 104 | + className={computedInputClassName} |
| 105 | + id={id ?? fallbackId} |
| 106 | + {...props} |
| 107 | + /> |
| 108 | + </div> |
60 | 109 | ); |
61 | 110 | }); |
62 | 111 |
|
|
0 commit comments