Skip to content

Commit 54a2ba6

Browse files
committed
Add labels to inputs
1 parent 0ffebda commit 54a2ba6

File tree

7 files changed

+114
-49
lines changed

7 files changed

+114
-49
lines changed

src/components/controls/Input.tsx

Lines changed: 79 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,111 @@
1-
import { ForwardedRef, forwardRef, InputHTMLAttributes, useMemo } from "react";
1+
import { ForwardedRef, forwardRef, InputHTMLAttributes, ReactNode, useId, useMemo } from "react";
22
import { cva, VariantProps as GetVariantProps } from "class-variance-authority";
33
import { twMerge } from "tailwind-merge";
44

55
import { RequiredKeys } from "@/types/utility";
66

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", {
810
variants: {
9-
variant: {
10-
outlined: "border-2"
11-
},
1211
color: {
13-
primary: "",
14-
secondary: ""
12+
primary: "border-primary bg-white",
13+
secondary: "border-secondary bg-white"
1514
},
1615
},
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-
],
2916
defaultVariants: {
30-
color: "primary",
31-
variant: "outlined"
17+
color: "primary"
3218
}
3319
});
3420

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+
3538
export type TagProps = Omit<InputHTMLAttributes<HTMLInputElement>, "color">;
3639
export type VariantProps = GetVariantProps<typeof style>;
37-
export type InputProps = TagProps & RequiredKeys<VariantProps, "color" | "variant">;
40+
export type InputProps = BaseProps & TagProps & RequiredKeys<VariantProps, "color">;
3841

3942
const Input = forwardRef((
4043
{
4144
className,
45+
inputClassName,
46+
labelClassName,
47+
fieldsetClassName,
48+
labelInset = "1.5rem",
49+
label,
4250
color,
43-
variant,
51+
id,
4452
...props
4553
}: InputProps,
4654
ref: ForwardedRef<HTMLInputElement>
4755
) => {
4856

57+
const fallbackId = useId();
58+
4959
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]
5292
);
5393

5494
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>
60109
);
61110
});
62111

src/components/controls/Label.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1+
import { cva } from "class-variance-authority";
12
import React, { DetailedHTMLProps, ForwardedRef, LabelHTMLAttributes, forwardRef, useMemo } from "react";
23
import { twMerge } from "tailwind-merge";
34

45
export interface LabelProps extends DetailedHTMLProps<LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement> {
56
htmlFor: string;
67
}
78

9+
export const style = cva("text-sm font-bold block");
10+
811
const Label = forwardRef(({ className, ...props }: LabelProps, ref: ForwardedRef<HTMLLabelElement>) => {
912

1013
const computedClassName = useMemo(
11-
() => twMerge("text-sm font-bold block", className),
14+
() => twMerge(style(), className),
1215
[className]
1316
);
1417

src/components/controls/TextArea.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,26 @@ import { twMerge } from "tailwind-merge";
33

44
import { RequiredKeys } from "@/types/utility";
55

6-
import { style, VariantProps as InputVariantProps } from "./Input";
6+
import { style as inputStyle, VariantProps as InputVariantProps } from "./Input";
7+
8+
export const style: typeof inputStyle = (props) => twMerge(inputStyle(props), "rounded-lg");
79

810
export type TagProps = DetailedHTMLProps<TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>;
911
export type VariantProps = InputVariantProps;
10-
export type TextAreaProps = TagProps & RequiredKeys<VariantProps, "color" | "variant">;
12+
export type TextAreaProps = TagProps & RequiredKeys<VariantProps, "color">;
1113

1214
const TextArea = forwardRef((
1315
{
1416
className,
1517
color,
16-
variant,
1718
...props
1819
}: TextAreaProps,
1920
ref: ForwardedRef<HTMLTextAreaElement>
2021
) => {
2122

2223
const computedClassName = useMemo(
23-
() => twMerge(style({ color, variant }), className),
24-
[className, color, variant]
24+
() => twMerge(style({ color }), className),
25+
[className, color]
2526
);
2627

2728
return (

src/components/pages/contribute/FeedbackSection.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { backend } from "@/utils/wretch";
1111
import TextArea from "@/components/controls/TextArea";
1212
import Form from "@/components/controls/Form";
1313
import Heading from "@/components/layout/Heading";
14+
import Label from "@/components/controls/Label";
1415

1516
const feedbackSchema = z.object({
1617
text: z.string()
@@ -65,14 +66,19 @@ const FeedbackSection = () => {
6566
successChildren={`🎉 ${response?.message} 🎉`}
6667
oneTime
6768
>
68-
<TextArea
69-
color="primary"
70-
variant="outlined"
71-
className="w-full p-4 text-lg rounded-lg md:text-xl h-fit min-h-[3.25em] max-h-[50ex]"
72-
placeholder="I would like this feature!"
73-
rows={5}
74-
{...register("text")}
75-
/>
69+
<div className="w-full">
70+
<Label htmlFor="message-input" className="text-start text-secondary">
71+
Your Message:
72+
</Label>
73+
<TextArea
74+
id="message-input"
75+
color="primary"
76+
className="w-full p-4 text-lg md:text-xl h-fit min-h-[3.25em] max-h-[50ex]"
77+
placeholder="I would like this feature!"
78+
rows={5}
79+
{...register("text")}
80+
/>
81+
</div>
7682
<Error className="w-full px-2 text-start" state={formState} name="text" />
7783
<Button type="submit" color="secondary" loading={loading} className="px-5 py-3 text-lg md:text-xl w-fit">
7884
Submit Feedback

src/components/pages/front/SignupSection.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import perks from "@/assets/state/perks";
88
import Button from "@/components/controls/Button";
99
import Error from "@/components/controls/Error";
1010
import Link from "@/components/navigation/Link";
11-
import { backend } from "@/utils/wretch";
1211
import Input from "@/components/controls/Input";
1312
import Form from "@/components/controls/Form";
1413
import Heading from "@/components/layout/Heading";
1514

15+
import { backend } from "@/utils/wretch";
1616

1717
const signupSchema = z.object({
1818
email: z.string().email().min(3)
@@ -78,10 +78,14 @@ const SignupSection = () => {
7878
>
7979
<div className="w-full">
8080
<Input
81+
id="signup-email-input"
8182
color="primary"
82-
variant="outlined"
83-
className="w-full p-4 text-lg md:text-xl"
83+
className="w-full text-lg md:text-xl"
84+
inputClassName="p-4"
8485
placeholder="your@email.com"
86+
label="Email:"
87+
labelInset="2rem"
88+
labelClassName="text-primary"
8589
{...register("email")}
8690
/>
8791
<Error className="w-full px-2 text-start" state={formState} name="email" />

src/pages/blog/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ const BlogPage: Page<BlogPageProps> = ({ articles, pathname }) => {
104104
id="search-input"
105105
type="text"
106106
color="secondary"
107-
variant="outlined"
108107
placeholder="Search..."
109108
{...register("search")}
110109
/>

src/pages/mail/unsubscribe.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import Form from "@/components/controls/Form";
1515
import Heading from "@/components/layout/Heading";
1616
import { makeOgMeta } from "@/utils/meta/opengraph";
1717
import { makeSitemapMeta } from "@/utils/meta/sitemap";
18+
import Label from "@/components/controls/Label";
1819

1920
const unsubscribeSchema = z.object({
2021
email: z.string().email().min(3)
@@ -65,9 +66,11 @@ const UnsubscribePage: Page = ({ pathname }) => {
6566
Once you unsubscribe you won't receive any more emails from us and your email will be immediately deleted from our records.
6667
</p>
6768
<Input
69+
id="email-input"
6870
color="secondary"
69-
variant="outlined"
7071
placeholder="your@email.com"
72+
label="Email:"
73+
labelClassName="text-secondary"
7174
{...register("email")}
7275
/>
7376
<Button type="submit" color="secondary">

0 commit comments

Comments
 (0)