Facturas de cliente
This commit is contained in:
parent
a5af168e6b
commit
1d1f412e6c
@ -57,6 +57,11 @@
|
|||||||
"placeholder": "Select a date",
|
"placeholder": "Select a date",
|
||||||
"description": "Invoice issue date"
|
"description": "Invoice issue date"
|
||||||
},
|
},
|
||||||
|
"invoice_series": {
|
||||||
|
"label": "Serie",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
"operation_date": {
|
"operation_date": {
|
||||||
"label": "Operation date",
|
"label": "Operation date",
|
||||||
"placeholder": "Select a date",
|
"placeholder": "Select a date",
|
||||||
|
|||||||
@ -57,6 +57,11 @@
|
|||||||
"placeholder": "Seleccionar una fecha",
|
"placeholder": "Seleccionar una fecha",
|
||||||
"description": "Fecha de emisión de la factura"
|
"description": "Fecha de emisión de la factura"
|
||||||
},
|
},
|
||||||
|
"invoice_series": {
|
||||||
|
"label": "Serie",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
"operation_date": {
|
"operation_date": {
|
||||||
"label": "Intervención",
|
"label": "Intervención",
|
||||||
"placeholder": "Seleccionar una fecha",
|
"placeholder": "Seleccionar una fecha",
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { Button } from "@repo/shadcn-ui/components";
|
import { Button } from "@repo/shadcn-ui/components";
|
||||||
import { PlusCircleIcon } from "lucide-react";
|
import { PlusCircleIcon } from "lucide-react";
|
||||||
import { JSX, forwardRef } from "react";
|
import { JSX, forwardRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "../../i18n";
|
||||||
import { MODULE_NAME } from "../../manifest";
|
|
||||||
|
|
||||||
export interface AppendEmptyRowButtonProps extends React.ComponentProps<typeof Button> {
|
export interface AppendEmptyRowButtonProps extends React.ComponentProps<typeof Button> {
|
||||||
label?: string;
|
label?: string;
|
||||||
@ -11,7 +10,7 @@ export interface AppendEmptyRowButtonProps extends React.ComponentProps<typeof B
|
|||||||
|
|
||||||
export const AppendEmptyRowButton = forwardRef<HTMLButtonElement, AppendEmptyRowButtonProps>(
|
export const AppendEmptyRowButton = forwardRef<HTMLButtonElement, AppendEmptyRowButtonProps>(
|
||||||
({ label, className, ...props }: AppendEmptyRowButtonProps, ref): JSX.Element => {
|
({ label, className, ...props }: AppendEmptyRowButtonProps, ref): JSX.Element => {
|
||||||
const { t } = useTranslation(MODULE_NAME);
|
const { t } = useTranslation();
|
||||||
const _label = label || t("common.append_empty_row");
|
const _label = label || t("common.append_empty_row");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { Badge } from "@repo/shadcn-ui/components";
|
import { Badge } from "@repo/shadcn-ui/components";
|
||||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
import { forwardRef } from "react";
|
import { forwardRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "../i18n";
|
||||||
import { MODULE_NAME } from "../manifest";
|
|
||||||
|
|
||||||
export type CustomerInvoiceStatus = "draft" | "emitted" | "sent" | "received" | "rejected";
|
export type CustomerInvoiceStatus = "draft" | "emitted" | "sent" | "received" | "rejected";
|
||||||
|
|
||||||
@ -42,7 +41,7 @@ export const CustomerInvoiceStatusBadge = forwardRef<
|
|||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
CustomerInvoiceStatusBadgeProps
|
CustomerInvoiceStatusBadgeProps
|
||||||
>(({ status, className, ...props }, ref) => {
|
>(({ status, className, ...props }, ref) => {
|
||||||
const { t } = useTranslation(MODULE_NAME);
|
const { t } = useTranslation();
|
||||||
const normalizedStatus = status.toLowerCase() as CustomerInvoiceStatus;
|
const normalizedStatus = status.toLowerCase() as CustomerInvoiceStatus;
|
||||||
const config = statusColorConfig[normalizedStatus];
|
const config = statusColorConfig[normalizedStatus];
|
||||||
const commonClassName = "transition-colors duration-200 cursor-pointer shadow-none rounded-full";
|
const commonClassName = "transition-colors duration-200 cursor-pointer shadow-none rounded-full";
|
||||||
|
|||||||
@ -11,14 +11,12 @@ import { MoneyDTO } from "@erp/core";
|
|||||||
import { formatDate, formatMoney } from "@erp/core/client";
|
import { formatDate, formatMoney } from "@erp/core/client";
|
||||||
// Core CSS
|
// Core CSS
|
||||||
import { AgGridReact } from "ag-grid-react";
|
import { AgGridReact } from "ag-grid-react";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useCustomerInvoicesQuery } from "../hooks";
|
import { useCustomerInvoicesQuery } from "../hooks";
|
||||||
import { MODULE_NAME } from "../manifest";
|
|
||||||
import { CustomerInvoiceStatusBadge } from "./customer-invoice-status-badge";
|
import { CustomerInvoiceStatusBadge } from "./customer-invoice-status-badge";
|
||||||
|
|
||||||
// Create new GridExample component
|
// Create new GridExample component
|
||||||
export const CustomerInvoicesListGrid = () => {
|
export const CustomerInvoicesListGrid = () => {
|
||||||
const { t } = useTranslation(MODULE_NAME);
|
const { t } = useTranslation();
|
||||||
const { data, isLoading, isPending, isError, error } = useCustomerInvoicesQuery({});
|
const { data, isLoading, isPending, isError, error } = useCustomerInvoicesQuery({});
|
||||||
|
|
||||||
// Column Definitions: Defines & controls grid columns.
|
// Column Definitions: Defines & controls grid columns.
|
||||||
|
|||||||
@ -1,19 +1,12 @@
|
|||||||
import {
|
import { FormControl, FormField, FormItem, FormMessage, Input } from "@repo/shadcn-ui/components";
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormMessage,
|
|
||||||
Input,
|
|
||||||
Textarea,
|
|
||||||
} from "@repo/shadcn-ui/components";
|
|
||||||
|
|
||||||
|
import { TextAreaField } from "@repo/rdx-ui/components";
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
import { ChevronDownIcon, ChevronUpIcon, CopyIcon, Trash2Icon } from "lucide-react";
|
import { ChevronDownIcon, ChevronUpIcon, CopyIcon, Trash2Icon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useFieldArray, useFormContext } from "react-hook-form";
|
import { useFieldArray, useFormContext } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useDetailColumns } from "../../hooks";
|
import { useDetailColumns } from "../../hooks";
|
||||||
import { MODULE_NAME } from "../../manifest";
|
import { useTranslation } from "../../i18n";
|
||||||
import { formatCurrency } from "../../pages/create/utils";
|
import { formatCurrency } from "../../pages/create/utils";
|
||||||
import {
|
import {
|
||||||
CustomerInvoiceItemsSortableDataTable,
|
CustomerInvoiceItemsSortableDataTable,
|
||||||
@ -29,7 +22,7 @@ export const CustomerInvoiceItemsCardEditor = ({
|
|||||||
//language: Language;
|
//language: Language;
|
||||||
defaultValues: Readonly<{ [x: string]: any }> | undefined;
|
defaultValues: Readonly<{ [x: string]: any }> | undefined;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation(MODULE_NAME);
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { control, watch, getValues } = useFormContext();
|
const { control, watch, getValues } = useFormContext();
|
||||||
|
|
||||||
@ -78,20 +71,11 @@ export const CustomerInvoiceItemsCardEditor = ({
|
|||||||
accessorKey: "description",
|
accessorKey: "description",
|
||||||
header: t("form_fields.items.description.label"),
|
header: t("form_fields.items.description.label"),
|
||||||
cell: ({ row: { index, original } }) => (
|
cell: ({ row: { index, original } }) => (
|
||||||
<FormField
|
<TextAreaField
|
||||||
control={control}
|
control={control}
|
||||||
name={`items.${index}.description`}
|
name={`items.${index}.description`}
|
||||||
render={({ field }) => (
|
placeholder={t("form_fields.items.description.placeholder")}
|
||||||
<FormItem className='md:col-span-2'>
|
className='md:col-span-2'
|
||||||
<FormControl>
|
|
||||||
<Textarea
|
|
||||||
placeholder={t("form_fields.items.description.placeholder")}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
minSize: 200,
|
minSize: 200,
|
||||||
|
|||||||
28
modules/customer-invoices/src/web/i18n.ts
Normal file
28
modules/customer-invoices/src/web/i18n.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useTranslation as useI18NextTranslation } from "react-i18next";
|
||||||
|
import enResources from "../common/locales/en.json";
|
||||||
|
import esResources from "../common/locales/es.json";
|
||||||
|
import { MODULE_NAME } from "./manifest";
|
||||||
|
|
||||||
|
const addMissingBundles = (i18n: any) => {
|
||||||
|
const needsEn = !i18n.hasResourceBundle("en", MODULE_NAME);
|
||||||
|
const needsEs = !i18n.hasResourceBundle("es", MODULE_NAME);
|
||||||
|
|
||||||
|
if (needsEn) {
|
||||||
|
i18n.addResourceBundle("en", MODULE_NAME, enResources, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsEs) {
|
||||||
|
i18n.addResourceBundle("es", MODULE_NAME, esResources, true, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useTranslation = () => {
|
||||||
|
const { i18n } = useI18NextTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
addMissingBundles(i18n);
|
||||||
|
}, [i18n]);
|
||||||
|
|
||||||
|
return useI18NextTranslation(MODULE_NAME);
|
||||||
|
};
|
||||||
@ -1,13 +1,12 @@
|
|||||||
import { AppBreadcrumb, AppContent } from "@repo/rdx-ui/components";
|
import { AppBreadcrumb, AppContent } from "@repo/rdx-ui/components";
|
||||||
import { Button } from "@repo/shadcn-ui/components";
|
import { Button } from "@repo/shadcn-ui/components";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useCreateCustomerInvoiceMutation } from "../../hooks";
|
import { useCreateCustomerInvoiceMutation } from "../../hooks";
|
||||||
import { MODULE_NAME } from "../../manifest";
|
import { useTranslation } from "../../i18n";
|
||||||
import { CustomerInvoiceEditForm } from "./customer-invoice-edit-form";
|
import { CustomerInvoiceEditForm } from "./customer-invoice-edit-form";
|
||||||
|
|
||||||
export const CustomerInvoiceCreate = () => {
|
export const CustomerInvoiceCreate = () => {
|
||||||
const { t } = useTranslation(MODULE_NAME);
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { mutate, isPending, isError, error } = useCreateCustomerInvoiceMutation();
|
const { mutate, isPending, isError, error } = useCreateCustomerInvoiceMutation();
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import * as z from "zod";
|
|||||||
|
|
||||||
import { ClientSelector } from "@erp/customers/components";
|
import { ClientSelector } from "@erp/customers/components";
|
||||||
|
|
||||||
import { DatePickerField } from "@repo/rdx-ui/components";
|
import { DatePickerInputField, TextAreaField, TextField } from "@repo/rdx-ui/components";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Calendar,
|
Calendar,
|
||||||
@ -35,9 +35,8 @@ import {
|
|||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { es } from "date-fns/locale";
|
import { es } from "date-fns/locale";
|
||||||
import { CalendarIcon, PlusIcon, Save, Trash2Icon, X } from "lucide-react";
|
import { CalendarIcon, PlusIcon, Save, Trash2Icon, X } from "lucide-react";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { CustomerInvoiceItemsCardEditor } from "../../components/items";
|
import { CustomerInvoiceItemsCardEditor } from "../../components/items";
|
||||||
import { MODULE_NAME } from "../../manifest";
|
import { useTranslation } from "../../i18n";
|
||||||
import { CustomerInvoiceData } from "./customer-invoice.schema";
|
import { CustomerInvoiceData } from "./customer-invoice.schema";
|
||||||
import { formatCurrency } from "./utils";
|
import { formatCurrency } from "./utils";
|
||||||
|
|
||||||
@ -219,7 +218,7 @@ export const CustomerInvoiceEditForm = ({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
isPending,
|
isPending,
|
||||||
}: InvoiceFormProps) => {
|
}: InvoiceFormProps) => {
|
||||||
const { t } = useTranslation(MODULE_NAME);
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const form = useForm<CustomerInvoiceData>({
|
const form = useForm<CustomerInvoiceData>({
|
||||||
resolver: zodResolver(invoiceSchema),
|
resolver: zodResolver(invoiceSchema),
|
||||||
@ -271,18 +270,13 @@ export const CustomerInvoiceEditForm = ({
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className='grid grid-cols-1 gap-4 space-y-6'>
|
<CardContent className='grid grid-cols-1 gap-4 space-y-6'>
|
||||||
<ClientSelector />
|
<ClientSelector />
|
||||||
<FormField
|
<TextField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name='customer_id'
|
name='customer_id'
|
||||||
render={({ field }) => (
|
required
|
||||||
<FormItem>
|
label={t("form_fields.customer_id.label")}
|
||||||
<FormLabel>ID Cliente</FormLabel>
|
placeholder={t("form_fields.customer_id.placeholder")}
|
||||||
<FormControl>
|
description={t("form_fields.customer_id.description")}
|
||||||
<Input placeholder='ID del cliente' {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@ -293,22 +287,19 @@ export const CustomerInvoiceEditForm = ({
|
|||||||
<CardTitle>Información Básica</CardTitle>
|
<CardTitle>Información Básica</CardTitle>
|
||||||
<CardDescription>Detalles generales de la factura</CardDescription>
|
<CardDescription>Detalles generales de la factura</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className='grid gap-6 md:grid-cols-6'>
|
<CardContent className='grid gap-y-6 gap-x-8 md:grid-cols-4'>
|
||||||
<FormField
|
<TextField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name='invoice_number'
|
name='invoice_number'
|
||||||
render={({ field }) => (
|
required
|
||||||
<FormItem>
|
disabled
|
||||||
<FormLabel>{t("form_fields.invoice_number.label")}</FormLabel>
|
readOnly
|
||||||
<FormControl>
|
label={t("form_fields.invoice_number.label")}
|
||||||
<Input placeholder={t("form_fields.invoice_number.placeholder")} {...field} />
|
placeholder={t("form_fields.invoice_number.placeholder")}
|
||||||
</FormControl>
|
description={t("form_fields.invoice_number.description")}
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DatePickerField
|
<DatePickerInputField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name='issue_date'
|
name='issue_date'
|
||||||
required
|
required
|
||||||
@ -317,18 +308,13 @@ export const CustomerInvoiceEditForm = ({
|
|||||||
description={t("form_fields.issue_date.description")}
|
description={t("form_fields.issue_date.description")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<TextField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name='invoice_series'
|
name='invoice_series'
|
||||||
render={({ field }) => (
|
required
|
||||||
<FormItem>
|
label={t("form_fields.invoice_series.label")}
|
||||||
<FormLabel>Serie</FormLabel>
|
placeholder={t("form_fields.invoice_series.placeholder")}
|
||||||
<FormControl>
|
description={t("form_fields.invoice_series.description")}
|
||||||
<Input placeholder='A' {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@ -391,6 +377,14 @@ export const CustomerInvoiceEditForm = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<TextAreaField
|
||||||
|
control={form.control}
|
||||||
|
name={`items.${index}.description`}
|
||||||
|
label={t("form_fields.items.description.label")}
|
||||||
|
placeholder={t("form_fields.items.description.placeholder")}
|
||||||
|
description={t("form_fields.items.description.description")}
|
||||||
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name={`items.${index}.quantity.amount`}
|
name={`items.${index}.quantity.amount`}
|
||||||
|
|||||||
@ -2,13 +2,12 @@ import { AppBreadcrumb, AppContent } from "@repo/rdx-ui/components";
|
|||||||
import { Button } from "@repo/shadcn-ui/components";
|
import { Button } from "@repo/shadcn-ui/components";
|
||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { CustomerInvoicesListGrid } from "../components";
|
import { CustomerInvoicesListGrid } from "../components";
|
||||||
import { MODULE_NAME } from "../manifest";
|
import { useTranslation } from "../i18n";
|
||||||
|
|
||||||
export const CustomerInvoicesList = () => {
|
export const CustomerInvoicesList = () => {
|
||||||
const { t } = useTranslation(MODULE_NAME);
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [status, setStatus] = useState("all");
|
const [status, setStatus] = useState("all");
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from "@repo/rdx-ui/locales/i18n.ts";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@ -9,7 +10,6 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@repo/shadcn-ui/components";
|
} from "@repo/shadcn-ui/components";
|
||||||
import { CellContext } from "@tanstack/react-table";
|
import { CellContext } from "@tanstack/react-table";
|
||||||
import { t } from "i18next";
|
|
||||||
import { MoreVerticalIcon } from "lucide-react";
|
import { MoreVerticalIcon } from "lucide-react";
|
||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
@ -34,6 +34,8 @@ export function DataTableRowActions<TData = any, TValue = unknown>({
|
|||||||
actions,
|
actions,
|
||||||
rowContext,
|
rowContext,
|
||||||
}: DataTableRowActionsProps<TData, TValue>) {
|
}: DataTableRowActionsProps<TData, TValue>) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
|||||||
@ -12,21 +12,22 @@ import {
|
|||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@repo/shadcn-ui/components";
|
} from "@repo/shadcn-ui/components";
|
||||||
import { CalendarIcon } from "lucide-react";
|
import { CalendarIcon, LockIcon } from "lucide-react";
|
||||||
|
|
||||||
import { useTranslation } from "@repo/rdx-ui/locales/i18n.ts";
|
|
||||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { Control, FieldPath, FieldValues } from "react-hook-form";
|
import { Control, FieldPath, FieldValues } from "react-hook-form";
|
||||||
|
import { useTranslation } from "../../locales/i18n.ts";
|
||||||
|
|
||||||
type DatePickerFieldProps<TFormValues extends FieldValues> = {
|
type DatePickerFieldProps<TFormValues extends FieldValues> = {
|
||||||
control: Control<TFormValues>;
|
control: Control<TFormValues>;
|
||||||
name: FieldPath<TFormValues>;
|
name: FieldPath<TFormValues>;
|
||||||
label: string;
|
label?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
|
readOnly?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
formatDateFn?: (iso: string) => string;
|
formatDateFn?: (iso: string) => string;
|
||||||
};
|
};
|
||||||
@ -39,32 +40,43 @@ export function DatePickerField<TFormValues extends FieldValues>({
|
|||||||
description,
|
description,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
required = false,
|
required = false,
|
||||||
|
readOnly = false,
|
||||||
className,
|
className,
|
||||||
formatDateFn = (iso) => format(new Date(iso), "dd/MM/yyyy"),
|
formatDateFn = (iso) => format(new Date(iso), "dd/MM/yyyy"),
|
||||||
}: DatePickerFieldProps<TFormValues>) {
|
}: DatePickerFieldProps<TFormValues>) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const isDisabled = disabled || readOnly;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
control={control}
|
control={control}
|
||||||
name={name}
|
name={name}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className={cn("space-y-2", className)}>
|
<FormItem className={cn("space-y-2", className)}>
|
||||||
<div className='flex justify-between items-center'>
|
{label && (
|
||||||
<FormLabel className='m-0'>{label}</FormLabel>
|
<div className='flex justify-between items-center'>
|
||||||
{required && <span className='text-xs text-destructive'>{t("common.required")}</span>}
|
<FormLabel className='m-0'>{label}</FormLabel>
|
||||||
</div>{" "}
|
{required && <span className='text-xs text-destructive'>{t("common.required")}</span>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Button
|
<Button
|
||||||
variant='outline'
|
variant='outline'
|
||||||
disabled={disabled}
|
disabled={isDisabled}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-full justify-start text-left font-normal",
|
"w-full justify-start text-left font-normal",
|
||||||
!field.value && "text-muted-foreground"
|
!field.value && "text-muted-foreground",
|
||||||
|
disabled && "bg-muted text-muted-foreground cursor-not-allowed",
|
||||||
|
readOnly && !disabled && "bg-muted text-foreground cursor-default"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CalendarIcon className='mr-2 h-4 w-4' />
|
{readOnly ? (
|
||||||
|
<LockIcon className='mr-2 h-4 w-4 opacity-70' />
|
||||||
|
) : (
|
||||||
|
<CalendarIcon className='mr-2 h-4 w-4' />
|
||||||
|
)}
|
||||||
{field.value ? formatDateFn(field.value) : placeholder}
|
{field.value ? formatDateFn(field.value) : placeholder}
|
||||||
</Button>
|
</Button>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -73,11 +85,16 @@ export function DatePickerField<TFormValues extends FieldValues>({
|
|||||||
<Calendar
|
<Calendar
|
||||||
mode='single'
|
mode='single'
|
||||||
selected={field.value ? new Date(field.value) : undefined}
|
selected={field.value ? new Date(field.value) : undefined}
|
||||||
onSelect={(date) => field.onChange(date?.toISOString())}
|
onSelect={(date) => {
|
||||||
|
if (!readOnly) {
|
||||||
|
field.onChange(date?.toISOString());
|
||||||
|
}
|
||||||
|
}}
|
||||||
initialFocus
|
initialFocus
|
||||||
/>
|
/>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
<p className={cn("text-xs text-muted-foreground", !description && "invisible")}>
|
<p className={cn("text-xs text-muted-foreground", !description && "invisible")}>
|
||||||
{description || "\u00A0"}
|
{description || "\u00A0"}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
155
packages/rdx-ui/src/components/form/DatePickerInputField.tsx
Normal file
155
packages/rdx-ui/src/components/form/DatePickerInputField.tsx
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import {
|
||||||
|
Calendar,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@repo/shadcn-ui/components";
|
||||||
|
import { CalendarIcon, LockIcon } from "lucide-react";
|
||||||
|
|
||||||
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
|
import { format, isValid, parse } from "date-fns";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Control, FieldPath, FieldValues } from "react-hook-form";
|
||||||
|
import { useTranslation } from "../../locales/i18n.ts";
|
||||||
|
|
||||||
|
type DatePickerInputFieldProps<TFormValues extends FieldValues> = {
|
||||||
|
control: Control<TFormValues>;
|
||||||
|
name: FieldPath<TFormValues>;
|
||||||
|
label: string;
|
||||||
|
placeholder?: string;
|
||||||
|
description?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
required?: boolean;
|
||||||
|
readOnly?: boolean;
|
||||||
|
className?: string;
|
||||||
|
formatDateFn?: (iso: string) => string;
|
||||||
|
parseDateFormat?: string; // e.g. "dd/MM/yyyy"
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DatePickerInputField<TFormValues extends FieldValues>({
|
||||||
|
control,
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
description,
|
||||||
|
disabled = false,
|
||||||
|
required = false,
|
||||||
|
readOnly = false,
|
||||||
|
className,
|
||||||
|
formatDateFn = (iso) => format(new Date(iso), "dd/MM/yyyy"),
|
||||||
|
parseDateFormat = "dd/MM/yyyy",
|
||||||
|
}: DatePickerInputFieldProps<TFormValues>) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const isDisabled = disabled;
|
||||||
|
const isReadOnly = readOnly && !disabled;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
control={control}
|
||||||
|
name={name}
|
||||||
|
render={({ field }) => {
|
||||||
|
const [inputValue, setInputValue] = useState<string>(
|
||||||
|
field.value ? formatDateFn(field.value) : ""
|
||||||
|
);
|
||||||
|
const [inputError, setInputError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const handleInputChange = (value: string) => {
|
||||||
|
setInputValue(value);
|
||||||
|
setInputError(null); // Reset error on typing
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateAndSetDate = () => {
|
||||||
|
const parsed = parse(inputValue, parseDateFormat, new Date());
|
||||||
|
if (isValid(parsed)) {
|
||||||
|
field.onChange(parsed.toISOString());
|
||||||
|
setInputError(null);
|
||||||
|
} else {
|
||||||
|
setInputError(t("common.invalid_date") || "Fecha no válida");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItem className={cn("space-y-2", className)}>
|
||||||
|
<div className='flex justify-between items-center'>
|
||||||
|
<FormLabel className='m-0'>{label}</FormLabel>
|
||||||
|
{required && <span className='text-xs text-destructive'>{t("common.required")}</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<FormControl>
|
||||||
|
<div className='relative'>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
value={inputValue}
|
||||||
|
onChange={(e) => handleInputChange(e.target.value)}
|
||||||
|
onBlur={validateAndSetDate}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
validateAndSetDate();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
readOnly={isReadOnly}
|
||||||
|
disabled={isDisabled}
|
||||||
|
className={cn(
|
||||||
|
"w-full rounded-md border px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring",
|
||||||
|
isDisabled && "bg-muted text-muted-foreground cursor-not-allowed",
|
||||||
|
isReadOnly && "bg-muted text-foreground cursor-default",
|
||||||
|
!isDisabled && !isReadOnly && "bg-white text-foreground",
|
||||||
|
inputError && "border-destructive ring-destructive"
|
||||||
|
)}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
<div className='absolute inset-y-0 right-2 flex items-center pointer-events-none'>
|
||||||
|
{isReadOnly ? (
|
||||||
|
<LockIcon className='h-4 w-4 text-muted-foreground' />
|
||||||
|
) : (
|
||||||
|
<CalendarIcon className='h-4 w-4 text-muted-foreground' />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverTrigger>
|
||||||
|
|
||||||
|
{!isDisabled && !isReadOnly && (
|
||||||
|
<PopoverContent className='w-auto p-0'>
|
||||||
|
<Calendar
|
||||||
|
mode='single'
|
||||||
|
selected={field.value ? new Date(field.value) : undefined}
|
||||||
|
onSelect={(date) => {
|
||||||
|
if (date) {
|
||||||
|
const iso = date.toISOString();
|
||||||
|
field.onChange(iso);
|
||||||
|
setInputValue(formatDateFn(iso));
|
||||||
|
setInputError(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
)}
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<p
|
||||||
|
className={cn(
|
||||||
|
"text-xs",
|
||||||
|
inputError ? "text-destructive" : "text-muted-foreground",
|
||||||
|
!description && !inputError && "invisible"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{inputError || description || "\u00A0"}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
64
packages/rdx-ui/src/components/form/NumberField.tsx
Normal file
64
packages/rdx-ui/src/components/form/NumberField.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// DatePickerField.tsx
|
||||||
|
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
Input,
|
||||||
|
} from "@repo/shadcn-ui/components";
|
||||||
|
|
||||||
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
|
import { Control, FieldPath, FieldValues } from "react-hook-form";
|
||||||
|
import { useTranslation } from "../../locales/i18n.ts";
|
||||||
|
|
||||||
|
type NumberFieldProps<TFormValues extends FieldValues> = {
|
||||||
|
control: Control<TFormValues>;
|
||||||
|
name: FieldPath<TFormValues>;
|
||||||
|
label: string;
|
||||||
|
placeholder?: string;
|
||||||
|
description?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
required?: boolean;
|
||||||
|
readOnly?: boolean;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function NumberField<TFormValues extends FieldValues>({
|
||||||
|
control,
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
description,
|
||||||
|
disabled = false,
|
||||||
|
required = false,
|
||||||
|
readOnly = false,
|
||||||
|
className,
|
||||||
|
}: NumberFieldProps<TFormValues>) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const isDisabled = disabled || readOnly;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
control={control}
|
||||||
|
name={name}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className={cn("space-y-2", className)}>
|
||||||
|
<div className='flex justify-between items-center'>
|
||||||
|
<FormLabel className='m-0'>{label}</FormLabel>
|
||||||
|
{required && <span className='text-xs text-destructive'>{t("common.required")}</span>}
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Input disabled={isDisabled} placeholder={placeholder} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<p className={cn("text-xs text-muted-foreground", !description && "invisible")}>
|
||||||
|
{description || "\u00A0"}
|
||||||
|
</p>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
66
packages/rdx-ui/src/components/form/TextAreaField.tsx
Normal file
66
packages/rdx-ui/src/components/form/TextAreaField.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// DatePickerField.tsx
|
||||||
|
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
Textarea,
|
||||||
|
} from "@repo/shadcn-ui/components";
|
||||||
|
|
||||||
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
|
import { Control, FieldPath, FieldValues } from "react-hook-form";
|
||||||
|
import { useTranslation } from "../../locales/i18n.ts";
|
||||||
|
|
||||||
|
type TextAreaFieldProps<TFormValues extends FieldValues> = {
|
||||||
|
control: Control<TFormValues>;
|
||||||
|
name: FieldPath<TFormValues>;
|
||||||
|
label?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
description?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
required?: boolean;
|
||||||
|
readOnly?: boolean;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function TextAreaField<TFormValues extends FieldValues>({
|
||||||
|
control,
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
description,
|
||||||
|
disabled = false,
|
||||||
|
required = false,
|
||||||
|
readOnly = false,
|
||||||
|
className,
|
||||||
|
}: TextAreaFieldProps<TFormValues>) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const isDisabled = disabled || readOnly;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
control={control}
|
||||||
|
name={name}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className={cn("space-y-2", className)}>
|
||||||
|
{label && (
|
||||||
|
<div className='flex justify-between items-center'>
|
||||||
|
<FormLabel className='m-0'>{label}</FormLabel>
|
||||||
|
{required && <span className='text-xs text-destructive'>{t("common.required")}</span>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<FormControl>
|
||||||
|
<Textarea disabled={isDisabled} placeholder={placeholder} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<p className={cn("text-xs text-muted-foreground", !description && "invisible")}>
|
||||||
|
{description || "\u00A0"}
|
||||||
|
</p>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
66
packages/rdx-ui/src/components/form/TextField.tsx
Normal file
66
packages/rdx-ui/src/components/form/TextField.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// DatePickerField.tsx
|
||||||
|
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
Input,
|
||||||
|
} from "@repo/shadcn-ui/components";
|
||||||
|
|
||||||
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
|
import { Control, FieldPath, FieldValues } from "react-hook-form";
|
||||||
|
import { useTranslation } from "../../locales/i18n.ts";
|
||||||
|
|
||||||
|
type TextFieldProps<TFormValues extends FieldValues> = {
|
||||||
|
control: Control<TFormValues>;
|
||||||
|
name: FieldPath<TFormValues>;
|
||||||
|
label?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
description?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
required?: boolean;
|
||||||
|
readOnly?: boolean;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function TextField<TFormValues extends FieldValues>({
|
||||||
|
control,
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
description,
|
||||||
|
disabled = false,
|
||||||
|
required = false,
|
||||||
|
readOnly = false,
|
||||||
|
className,
|
||||||
|
}: TextFieldProps<TFormValues>) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const isDisabled = disabled || readOnly;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
control={control}
|
||||||
|
name={name}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className={cn("space-y-2", className)}>
|
||||||
|
{label && (
|
||||||
|
<div className='flex justify-between items-center'>
|
||||||
|
<FormLabel className='m-0'>{label}</FormLabel>
|
||||||
|
{required && <span className='text-xs text-destructive'>{t("common.required")}</span>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<FormControl>
|
||||||
|
<Input disabled={isDisabled} placeholder={placeholder} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<p className={cn("text-xs text-muted-foreground", !description && "invisible")}>
|
||||||
|
{description || "\u00A0"}
|
||||||
|
</p>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1 +1,4 @@
|
|||||||
export * from "./DatePickerField.tsx";
|
export * from "./DatePickerField.tsx";
|
||||||
|
export * from "./DatePickerInputField.tsx";
|
||||||
|
export * from "./TextAreaField.tsx";
|
||||||
|
export * from "./TextField.tsx";
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"common": {
|
"common": {
|
||||||
|
"actions": "Actions",
|
||||||
|
"invalid_date": "Invalid date",
|
||||||
"required": "required"
|
"required": "required"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"common": {
|
"common": {
|
||||||
|
"actions": "Actions",
|
||||||
|
"invalid_date": "Fecha incorrecta o no válida",
|
||||||
"required": "obligatorio"
|
"required": "obligatorio"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
|
|||||||
@ -1,14 +1,28 @@
|
|||||||
import i18next from "i18next";
|
import { useEffect } from "react";
|
||||||
import { useTranslation as useTrans } from "react-i18next";
|
import { useTranslation as useI18NextTranslation } from "react-i18next";
|
||||||
import { PACKAGE_NAME } from "../index.ts";
|
import { PACKAGE_NAME } from "../index.ts";
|
||||||
import enResources from "./en.json" with { type: "json" };
|
import enResources from "./en.json" with { type: "json" };
|
||||||
import esResources from "./es.json" with { type: "json" };
|
import esResources from "./es.json" with { type: "json" };
|
||||||
|
|
||||||
export const useTranslation = () => {
|
const addMissingBundles = (i18n: any) => {
|
||||||
if (!i18next.hasLoadedNamespace(PACKAGE_NAME)) {
|
const needsEn = !i18n.hasResourceBundle("en", PACKAGE_NAME);
|
||||||
i18next.addResourceBundle("en", PACKAGE_NAME, enResources, true, true);
|
const needsEs = !i18n.hasResourceBundle("es", PACKAGE_NAME);
|
||||||
i18next.addResourceBundle("es", PACKAGE_NAME, esResources, true, true);
|
|
||||||
|
if (needsEn) {
|
||||||
|
i18n.addResourceBundle("en", PACKAGE_NAME, enResources, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return useTrans(PACKAGE_NAME);
|
if (needsEs) {
|
||||||
|
i18n.addResourceBundle("es", PACKAGE_NAME, esResources, true, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useTranslation = () => {
|
||||||
|
const { i18n } = useI18NextTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
addMissingBundles(i18n);
|
||||||
|
}, [i18n]);
|
||||||
|
|
||||||
|
return useI18NextTranslation(PACKAGE_NAME);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@repo/shadcn-ui/lib/utils"
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
|
|
||||||
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
data-slot="input"
|
data-slot='input'
|
||||||
className={cn(
|
className={cn(
|
||||||
|
"bg-background text-foreground",
|
||||||
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
@ -15,7 +16,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Input }
|
export { Input };
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user