Skip to content

Instantly share code, notes, and snippets.

@Sutil
Last active June 5, 2025 09:50
Show Gist options
  • Save Sutil/5285f2e5a912dcf14fc23393dac97fed to your computer and use it in GitHub Desktop.
Save Sutil/5285f2e5a912dcf14fc23393dac97fed to your computer and use it in GitHub Desktop.
Shandcn UI Money Mask Input - NextJS.
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import MoneyInput from "src/components/custom/money-input";
import { Button } from "src/components/ui/button";
import { Form } from "src/components/ui/form";
import * as z from "zod";
const schema = z.object({
value: z.coerce.number().min(0.01, "Required"),
});
export default function PlanForm() {
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
defaultValues: {
value: 0,
},
mode: "onTouched",
});
function onSubmit(values: z.infer<typeof schema>) {
// handle submit
}
return (
<Form {...form}>
<form
className="flex flex-col gap-8"
onSubmit={form.handleSubmit(onSubmit)}
>
<MoneyInput
form={form}
label="Valor"
name="value"
placeholder="Valor do plano"
/>
<Button type="submit" disabled={!form.formState.isValid}>
Submit
</Button>
</form>
</Form>
);
}
"use client";
import { useReducer } from "react";
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "../ui/form"; // Shadcn UI import
import { Input } from "../ui/input"; // Shandcn UI Input
import { UseFormReturn } from "react-hook-form";
type TextInputProps = {
form: UseFormReturn<any>;
name: string;
label: string;
placeholder: string;
};
// Brazilian currency config
const moneyFormatter = Intl.NumberFormat("pt-BR", {
currency: "BRL",
currencyDisplay: "symbol",
currencySign: "standard",
style: "currency",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
export default function MoneyInput(props: TextInputProps) {
const initialValue = props.form.getValues()[props.name]
? moneyFormatter.format(props.form.getValues()[props.name])
: "";
const [value, setValue] = useReducer((_: any, next: string) => {
const digits = next.replace(/\D/g, "");
return moneyFormatter.format(Number(digits) / 100);
}, initialValue);
function handleChange(realChangeFn: Function, formattedValue: string) {
const digits = formattedValue.replace(/\D/g, "");
const realValue = Number(digits) / 100;
realChangeFn(realValue);
}
return (
<FormField
control={props.form.control}
name={props.name}
render={({ field }) => {
field.value = value;
const _change = field.onChange;
return (
<FormItem>
<FormLabel>{props.label}</FormLabel>
<FormControl>
<Input
placeholder={props.placeholder}
type="text"
{...field}
onChange={(ev) => {
setValue(ev.target.value);
handleChange(_change, ev.target.value);
}}
value={value}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
);
}
@ZakHargz
Copy link

ZakHargz commented Jun 5, 2025

How would this work for negative values also?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment