mirror of
https://github.com/TheRealOwenRees/echecsfrance.git
synced 2026-07-22 20:16:57 +00:00
Factorise Button and Input components
This commit is contained in:
Vendored
+5
-1
@@ -53,5 +53,9 @@
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"css.lint.unknownAtRules": "ignore",
|
||||
"tailwindCSS.experimental.classRegex": ["[a-zA-Z]*ClassName=\"([^\"]+)\""]
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["[a-zA-Z]*ClassName=\"([^\"]+)\""],
|
||||
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
||||
["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"@turf/boolean-point-in-polygon": "^7.0.0-alpha.114",
|
||||
"@types/node": "^22.7.2",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"geojson": "^0.5.0",
|
||||
"jotai": "^2.6.1",
|
||||
|
||||
@@ -8,6 +8,7 @@ import dynamic from "next/dynamic";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button } from "@/components/Button";
|
||||
import { clearMessage } from "@/components/InfoMessage";
|
||||
import InfoMessage from "@/components/InfoMessage";
|
||||
import LoadingMap from "@/components/LoadingMap";
|
||||
@@ -174,12 +175,14 @@ const TournamentForm = () => {
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-4 row-span-2 sm:col-span-2">
|
||||
|
||||
<div className="col-span-4 row-span-2 self-stretch sm:col-span-2">
|
||||
<TextAreaField
|
||||
className="h-full"
|
||||
childrenWrapperClassName="h-full"
|
||||
name="message"
|
||||
control={form.control}
|
||||
label={t("messageLabel")}
|
||||
rows={6}
|
||||
placeholder={t("messagePlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
@@ -207,21 +210,16 @@ const TournamentForm = () => {
|
||||
<Map />
|
||||
</section>
|
||||
</div>
|
||||
<button
|
||||
disabled={form.formState.isSubmitting}
|
||||
type="submit"
|
||||
className="rounded-lg bg-primary-600 px-5 py-3 text-center text-sm font-medium text-white hover:bg-primary-800 focus:outline-none focus:ring-4 focus:ring-primary-300 disabled:opacity-25 dark:text-white dark:hover:bg-primary-700 dark:focus:ring-primary-800 sm:w-fit"
|
||||
>
|
||||
{form.formState.isSubmitting ? t("sending") : t("sendButton")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => form.reset()}
|
||||
type="button"
|
||||
className="ml-4 rounded-lg bg-primary-600 px-5 py-3 text-center text-sm font-medium text-white hover:bg-primary-800 focus:outline-none focus:ring-4 focus:ring-primary-300 disabled:opacity-25 dark:text-white dark:hover:bg-primary-700 dark:focus:ring-primary-800 sm:w-fit"
|
||||
>
|
||||
{t("clearForm")}
|
||||
</button>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<Button disabled={form.formState.isSubmitting} type="submit">
|
||||
{form.formState.isSubmitting ? t("sending") : t("sendButton")}
|
||||
</Button>
|
||||
|
||||
<Button onClick={() => form.reset()} type="button" intent="secondary">
|
||||
{t("clearForm")}
|
||||
</Button>
|
||||
</div>
|
||||
<InfoMessage responseMessage={responseMessage} />
|
||||
</form>
|
||||
</FormProvider>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useTranslations } from "next-intl";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button } from "@/components/Button";
|
||||
import InfoMessage from "@/components/InfoMessage";
|
||||
import { clearMessage } from "@/components/InfoMessage";
|
||||
import { TextAreaField } from "@/components/form/TextAreaField";
|
||||
@@ -77,13 +78,9 @@ const ContactForm = () => {
|
||||
required
|
||||
/>
|
||||
|
||||
<button
|
||||
disabled={form.formState.isSubmitting}
|
||||
type="submit"
|
||||
className="rounded-lg bg-primary-600 px-5 py-3 text-center text-sm font-medium text-white hover:bg-primary-800 focus:outline-none focus:ring-4 focus:ring-primary-300 disabled:opacity-25 dark:text-white dark:hover:bg-primary-700 dark:focus:ring-primary-800 sm:w-fit"
|
||||
>
|
||||
<Button disabled={form.formState.isSubmitting} type="submit">
|
||||
{form.formState.isSubmitting ? t("sending") : t("sendButton")}
|
||||
</button>
|
||||
</Button>
|
||||
|
||||
<InfoMessage responseMessage={responseMessage} />
|
||||
</form>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useState } from "react";
|
||||
import { signOut } from "next-auth/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Button } from "@/components/Button";
|
||||
import InfoMessage from "@/components/InfoMessage";
|
||||
import { deleteAccount } from "@/server/deleteAccount";
|
||||
import { useRouter } from "@/utils/routing";
|
||||
@@ -67,21 +68,21 @@ export default function Contact() {
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-center space-x-4 text-sm font-bold">
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
className="rounded-lg border border-primary px-5 py-3 text-center text-xs text-primary hover:bg-primary hover:text-white focus:outline-none focus:ring-4 focus:ring-primary-300 disabled:opacity-25 dark:text-white dark:focus:ring-primary-800 sm:w-fit"
|
||||
intent="secondary"
|
||||
>
|
||||
{at("cancelButton")}
|
||||
</button>
|
||||
</Button>
|
||||
|
||||
<button
|
||||
<Button
|
||||
onClick={() => onDeleteAccount()}
|
||||
disabled={deleting}
|
||||
className="rounded-lg bg-primary-600 px-5 py-3 text-center text-sm font-medium text-white hover:bg-primary-800 focus:outline-none focus:ring-4 focus:ring-primary-300 disabled:opacity-25 dark:text-white dark:hover:bg-primary-700 dark:focus:ring-primary-800 sm:w-fit"
|
||||
intent="primary"
|
||||
>
|
||||
{t("deleteButton")}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { IoAdd, IoCloseOutline } from "react-icons/io5";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button } from "@/components/Button";
|
||||
import { RadioGroupField } from "@/components/form/RadioGroupField";
|
||||
import { SelectField } from "@/components/form/SelectField";
|
||||
import { TextField } from "@/components/form/TextField";
|
||||
@@ -176,26 +177,31 @@ export const ManualEloForm = () => {
|
||||
}))}
|
||||
required
|
||||
/>
|
||||
|
||||
{gameFields.length > 1 && (
|
||||
<button
|
||||
className="hidden h-8 w-8 items-center justify-center rounded-md bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-600 dark:hover:bg-neutral-700 sm:flex"
|
||||
<Button
|
||||
intent="tertiary"
|
||||
size="compacted"
|
||||
className="hidden h-8 w-8 sm:flex"
|
||||
type="button"
|
||||
onClick={() => removeGame(i)}
|
||||
>
|
||||
<IoCloseOutline className="h-6 w-6 text-gray-900 transition-all duration-200 hover:text-primary dark:text-neutral-400 hover:dark:text-white" />
|
||||
</button>
|
||||
<IoCloseOutline className="h-6 w-6" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{gameFields.length > 1 && (
|
||||
<button
|
||||
className="flex h-8 w-8 items-center justify-center rounded-md bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-600 dark:hover:bg-neutral-700 sm:hidden"
|
||||
<Button
|
||||
intent="tertiary"
|
||||
size="compacted"
|
||||
className="flex h-8 w-8 sm:hidden"
|
||||
type="button"
|
||||
onClick={() => removeGame(i)}
|
||||
>
|
||||
<IoCloseOutline className="h-6 w-6 text-gray-900 transition-all duration-200 hover:text-primary dark:text-neutral-400 hover:dark:text-white" />
|
||||
</button>
|
||||
<IoCloseOutline className="h-6 w-6" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -244,26 +250,26 @@ export const ManualEloForm = () => {
|
||||
|
||||
<div className="mt-8 flex justify-end gap-4">
|
||||
{!isDefault && (
|
||||
<button
|
||||
<Button
|
||||
onClick={() => {
|
||||
form.reset();
|
||||
}}
|
||||
type="button"
|
||||
className="px-5 py-3 text-center text-sm font-medium text-primary-600 hover:text-primary-800 focus:outline-none focus:ring-4 focus:ring-primary-300 disabled:opacity-25 dark:text-white dark:hover:text-primary-700 dark:focus:ring-primary-800 sm:w-fit"
|
||||
intent="secondary"
|
||||
>
|
||||
{t("clearButton")}
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
<button
|
||||
<Button
|
||||
onClick={() => {
|
||||
appendGame({});
|
||||
}}
|
||||
type="button"
|
||||
className="rounded-lg bg-primary-600 px-5 py-3 text-center text-sm font-medium text-white hover:bg-primary-800 focus:outline-none focus:ring-4 focus:ring-primary-300 disabled:opacity-25 dark:text-white dark:hover:bg-primary-700 dark:focus:ring-primary-800 sm:w-fit"
|
||||
intent="primary"
|
||||
>
|
||||
<IoAdd className="mr-2 inline-block h-5 w-5" />
|
||||
{t("addGameButton")}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button } from "@/components/Button";
|
||||
import { ErrorBox } from "@/components/ErrorBox";
|
||||
import { Spinner } from "@/components/Spinner";
|
||||
import { TranslatedError } from "@/components/TranslatedError";
|
||||
@@ -176,7 +177,7 @@ export default function Elo() {
|
||||
|
||||
return (
|
||||
<section className="grid place-items-center bg-white pb-20 dark:bg-gray-800">
|
||||
<div className="max-w-xl px-4 py-8 lg:py-16">
|
||||
<div className="max-w-xxl px-4 py-8 lg:py-16">
|
||||
<h2
|
||||
className="mb-10 text-center text-4xl font-extrabold tracking-tight text-gray-900 dark:text-white"
|
||||
data-test="header2"
|
||||
@@ -189,13 +190,14 @@ export default function Elo() {
|
||||
|
||||
{hasTournamentId && !isFetching && !error && (
|
||||
<div className="mx-auto mb-8 flex justify-center">
|
||||
<button
|
||||
<Button
|
||||
intent="secondary"
|
||||
type="button"
|
||||
onClick={clearForm}
|
||||
className="rounded-lg border border-primary px-3 py-2 text-center text-sm text-primary hover:bg-primary hover:text-white focus:outline-none focus:ring-4 focus:ring-primary-300 disabled:opacity-25 dark:text-white dark:focus:ring-primary-800 sm:w-fit"
|
||||
>
|
||||
{t("enterManualResultsButton")}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ const TournamentTable = () => {
|
||||
classNameOverrides={{
|
||||
container: () => "flex flex-1",
|
||||
}}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useTranslations } from "next-intl";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button } from "@/components/Button";
|
||||
import { InlineSwitchField } from "@/components/form/InlineSwitchField";
|
||||
import { Label } from "@/components/form/Label";
|
||||
import { TextField } from "@/components/form/TextField";
|
||||
@@ -110,20 +111,13 @@ export const ZoneForm = ({
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-4">
|
||||
<button
|
||||
onClick={onCancel}
|
||||
className="rounded-lg border border-primary px-3 py-2 text-center text-xs text-primary hover:bg-primary hover:text-white focus:outline-none focus:ring-4 focus:ring-primary-300 disabled:opacity-25 dark:text-white dark:focus:ring-primary-800 sm:w-fit"
|
||||
>
|
||||
<Button intent="secondary" onClick={onCancel}>
|
||||
{cancelTitle ?? at("cancelButton")}
|
||||
</button>
|
||||
</Button>
|
||||
|
||||
<button
|
||||
disabled={form.formState.isSubmitting}
|
||||
type="submit"
|
||||
className="rounded-lg bg-primary-600 px-5 py-3 text-center text-sm font-medium text-white hover:bg-primary-800 focus:outline-none focus:ring-4 focus:ring-primary-300 disabled:opacity-25 dark:text-white dark:hover:bg-primary-700 dark:focus:ring-primary-800 sm:w-fit"
|
||||
>
|
||||
<Button disabled={form.formState.isSubmitting} type="submit">
|
||||
{submitTitle ?? at("saveButton")}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useFormatter, useTranslations } from "next-intl";
|
||||
import dynamic from "next/dynamic";
|
||||
import { IoAdd } from "react-icons/io5";
|
||||
|
||||
import { Button, buttonVariants } from "@/components/Button";
|
||||
import InfoMessage, { clearMessage } from "@/components/InfoMessage";
|
||||
import { Modal } from "@/components/Modal";
|
||||
import { Spinner } from "@/components/Spinner";
|
||||
@@ -116,17 +117,18 @@ const Zones = () => {
|
||||
pathname: "/zones/edit/[id]",
|
||||
params: { id: zone.id },
|
||||
}}
|
||||
className="rounded-lg border border-primary px-3 py-2 text-center text-xs text-primary hover:bg-primary hover:text-white focus:outline-none focus:ring-4 focus:ring-primary-300 disabled:opacity-25 dark:text-white dark:focus:ring-primary-800 sm:w-fit"
|
||||
className={buttonVariants({ intent: "secondary" })}
|
||||
>
|
||||
{at("editButton")}
|
||||
</Link>
|
||||
<button
|
||||
|
||||
<Button
|
||||
intent="secondary"
|
||||
type="button"
|
||||
onClick={() => setDeletingZoneId(zone.id)}
|
||||
className="rounded-lg border border-primary px-3 py-2 text-center text-xs text-primary hover:bg-primary hover:text-white focus:outline-none focus:ring-4 focus:ring-primary-300 disabled:opacity-25 dark:text-white dark:focus:ring-primary-800 sm:w-fit"
|
||||
>
|
||||
{at("deleteButton")}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -155,19 +157,15 @@ const Zones = () => {
|
||||
<InfoMessage responseMessage={responseMessage} />
|
||||
|
||||
<div className="flex items-center justify-between space-x-4 text-sm font-bold">
|
||||
<button
|
||||
<Button
|
||||
intent="secondary"
|
||||
type="button"
|
||||
onClick={() => setDeletingZoneId(null)}
|
||||
className="rounded-lg border border-primary px-3 py-2 text-center text-xs text-primary hover:bg-primary hover:text-white focus:outline-none focus:ring-4 focus:ring-primary-300 disabled:opacity-25 dark:text-white dark:focus:ring-primary-800 sm:w-fit"
|
||||
>
|
||||
{at("cancelButton")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onDelete()}
|
||||
className="rounded-lg bg-primary-600 px-5 py-3 text-center text-sm font-medium text-white hover:bg-primary-800 focus:outline-none focus:ring-4 focus:ring-primary-300 disabled:opacity-25 dark:text-white dark:hover:bg-primary-700 dark:focus:ring-primary-800 sm:w-fit"
|
||||
>
|
||||
{at("deleteButton")}
|
||||
</button>
|
||||
</Button>
|
||||
|
||||
<Button onClick={() => onDelete()}>{at("deleteButton")}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import { Menu } from "@headlessui/react";
|
||||
import { Button, Menu } from "@headlessui/react";
|
||||
import { signIn, signOut, useSession } from "next-auth/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { type VariantProps, cva } from "class-variance-authority";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export const buttonVariants = cva(
|
||||
"flex items-center justify-center transition-all duration-200",
|
||||
{
|
||||
variants: {
|
||||
intent: {
|
||||
primary: [
|
||||
"text-center",
|
||||
"text-white",
|
||||
"focus:outline-none focus:ring-2 focus:ring-primary-500",
|
||||
"bg-primary-600",
|
||||
"hover:bg-primary-800",
|
||||
"disabled:opacity-25",
|
||||
"dark:text-white dark:hover:bg-primary-700",
|
||||
],
|
||||
|
||||
secondary: [
|
||||
"text-primary dark:text-white",
|
||||
"hover:text-white dark:hover:text-white",
|
||||
"hover:bg-primary hover:text-white",
|
||||
"border border-primary",
|
||||
"focus:outline-none focus:ring-2 focus:ring-primary-500",
|
||||
"disabled:opacity-25",
|
||||
],
|
||||
|
||||
tertiary: [
|
||||
"text-gray-900 dark:text-neutral-400",
|
||||
"hover:text-primary dark:hover:text-white",
|
||||
"focus:outline-none focus:ring-2 focus:ring-primary-500",
|
||||
"disabled:opacity-25",
|
||||
],
|
||||
},
|
||||
size: {
|
||||
small: ["text-sm font-medium", "px-3 py-2", "sm:w-fit", "rounded-lg"],
|
||||
medium: ["text-sm font-medium", "px-5 py-3", "sm:w-fit", "rounded-lg"],
|
||||
compacted: ["rounded-none", "px-0 py-0"],
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
intent: "primary",
|
||||
size: "medium",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export type ButtonVariants = VariantProps<typeof buttonVariants>;
|
||||
export type ButtonProps = React.ComponentProps<"button"> & ButtonVariants;
|
||||
|
||||
export const Button = ({
|
||||
className,
|
||||
intent,
|
||||
size,
|
||||
...props
|
||||
}: ButtonProps & { className?: string }) => (
|
||||
<button
|
||||
className={twMerge(buttonVariants({ intent, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
import { Float } from "@headlessui-float/react";
|
||||
import { Menu } from "@headlessui/react";
|
||||
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
|
||||
import { IoMdMore } from "react-icons/io";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
@@ -37,15 +37,15 @@ export const DropdownMenu = ({
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
{buttonComponent ?? (
|
||||
<Menu.Button>
|
||||
<MenuButton>
|
||||
<IoMdMore />
|
||||
</Menu.Button>
|
||||
</MenuButton>
|
||||
)}
|
||||
|
||||
<Menu.Items className="z-20 flex w-auto items-start overflow-hidden rounded-md bg-neutral-200 focus:outline-none dark:bg-neutral-600">
|
||||
<MenuItems className="z-20 flex w-auto items-start overflow-hidden rounded-md bg-neutral-200 focus:outline-none dark:bg-neutral-600">
|
||||
<div className="flex flex-col px-1 py-1">
|
||||
{items.map(({ title, onClick, className, disabled }, i) => (
|
||||
<Menu.Item key={i}>
|
||||
<MenuItem key={i}>
|
||||
<button
|
||||
type="button"
|
||||
className={twMerge(
|
||||
@@ -61,10 +61,10 @@ export const DropdownMenu = ({
|
||||
>
|
||||
{title}
|
||||
</button>
|
||||
</Menu.Item>
|
||||
</MenuItem>
|
||||
))}
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</MenuItems>
|
||||
</Float>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
@@ -16,6 +16,8 @@ import MarkerClusterGroup from "react-leaflet-cluster";
|
||||
import { debouncedHoveredListIdAtom, mapBoundsAtom } from "@/atoms";
|
||||
import { MapEvents } from "@/components/MapEvents";
|
||||
|
||||
import { Button } from "./Button";
|
||||
|
||||
export type MarkerRef = {
|
||||
getMarker: () => L.Marker<any>;
|
||||
bounce: () => void;
|
||||
@@ -238,12 +240,14 @@ export const Map = ({
|
||||
</MapContainer>
|
||||
|
||||
<div className="flex items-center justify-center lg:hidden">
|
||||
<button
|
||||
className="p-3 text-primary-900 dark:text-white"
|
||||
<Button
|
||||
intent="tertiary"
|
||||
size="compacted"
|
||||
className="p-3"
|
||||
onClick={onScrollToTable}
|
||||
>
|
||||
<FaAngleDoubleDown />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,9 @@ import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { searchStringAtom } from "@/atoms";
|
||||
|
||||
import { Button } from "./Button";
|
||||
import { Input } from "./form/Input";
|
||||
|
||||
type SearchBarProps = {
|
||||
className?: string;
|
||||
};
|
||||
@@ -27,38 +30,38 @@ const SearchBar = ({ className }: SearchBarProps) => {
|
||||
<label htmlFor="table-search" className="sr-only">
|
||||
{t("searchLabel")}
|
||||
</label>
|
||||
|
||||
<div className="relative">
|
||||
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<svg
|
||||
className="h-5 w-5 text-gray-500 dark:text-gray-400"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{searchString !== "" && (
|
||||
<div className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-900 dark:text-white">
|
||||
<button onClick={() => setSearchString("")}>
|
||||
<IoCloseOutline className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<input
|
||||
<Input
|
||||
type="search"
|
||||
id="table-search"
|
||||
className={twMerge(
|
||||
"block rounded-lg border border-gray-300 bg-gray-50 p-2.5 px-10 text-gray-900 focus:border-primary-500 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-primary-500",
|
||||
className,
|
||||
)}
|
||||
placeholder={t("searchPlaceholder")}
|
||||
size="small"
|
||||
startIcon={
|
||||
<svg
|
||||
className="h-5 w-5 text-gray-500 dark:text-gray-400"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
}
|
||||
endIcon={
|
||||
searchString !== "" && (
|
||||
<Button
|
||||
intent="tertiary"
|
||||
size="compacted"
|
||||
onClick={() => setSearchString("")}
|
||||
>
|
||||
<IoCloseOutline className="h-5 w-5" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
value={searchString}
|
||||
onChange={(e) => updateSearchString(e.target.value)}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { Button } from "@headlessui/react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { signIn } from "next-auth/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
@@ -91,13 +92,9 @@ export const SignInForm = ({ callbackPath }: SignInFormProps) => {
|
||||
required
|
||||
/>
|
||||
|
||||
<button
|
||||
disabled={form.formState.isSubmitting}
|
||||
type="submit"
|
||||
className="rounded-lg bg-primary-600 px-5 py-3 text-center text-sm font-medium text-white hover:bg-primary-800 focus:outline-none focus:ring-4 focus:ring-primary-300 disabled:opacity-25 dark:text-white dark:hover:bg-primary-700 dark:focus:ring-primary-800 sm:w-fit"
|
||||
>
|
||||
<Button disabled={form.formState.isSubmitting} type="submit">
|
||||
{t("signInButton")}
|
||||
</button>
|
||||
</Button>
|
||||
|
||||
<InfoMessage
|
||||
className="text-center"
|
||||
|
||||
@@ -15,7 +15,7 @@ import AsyncSelect from "react-select/async";
|
||||
import { Prettify } from "@/types";
|
||||
|
||||
import { Field, GenericFieldProps } from "./Field";
|
||||
import { BaseOption, classNames } from "./Select";
|
||||
import { BaseOption, createSelectClassNamesFn } from "./Select";
|
||||
|
||||
export type AsyncSelectFieldProps<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
@@ -34,6 +34,7 @@ export type AsyncSelectFieldProps<
|
||||
loadOptions: (searchString?: string) => Promise<BaseOption<T, D>[]>;
|
||||
onInformChange?: (data: BaseOption<T, D>[] | null) => void;
|
||||
separators?: boolean;
|
||||
size?: "small" | "medium";
|
||||
}
|
||||
>;
|
||||
|
||||
@@ -50,6 +51,7 @@ export const AsyncSelectField = <
|
||||
label,
|
||||
hideErrorMessage,
|
||||
required,
|
||||
size,
|
||||
|
||||
loadOption,
|
||||
loadOptions,
|
||||
@@ -132,6 +134,10 @@ export const AsyncSelectField = <
|
||||
return options;
|
||||
};
|
||||
|
||||
const classNames = createSelectClassNamesFn<BaseOption<T, D>, IsMulti>({
|
||||
size,
|
||||
});
|
||||
|
||||
return (
|
||||
<Field {...{ name, control, className, label, hideErrorMessage, required }}>
|
||||
<Controller
|
||||
@@ -184,10 +190,7 @@ export const AsyncSelectField = <
|
||||
},
|
||||
}),
|
||||
}}
|
||||
classNames={classNames<BaseOption<T, D>, IsMulti>(
|
||||
hasError,
|
||||
separators ?? false,
|
||||
)}
|
||||
classNames={classNames(hasError, separators ?? false)}
|
||||
{...selectProps}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,8 @@ import { ReactDatePickerCustomHeaderProps } from "react-datepicker";
|
||||
import { IoChevronBack, IoChevronForward } from "react-icons/io5";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Button } from "@/components/Button";
|
||||
|
||||
const chevronClasses = {
|
||||
chevronRoot: "h-4 w-4 dark:text-white",
|
||||
chevronClassDisabled: "!text-neutral-500",
|
||||
@@ -23,7 +25,9 @@ export const DatePickerCustomHeader = ({
|
||||
|
||||
return (
|
||||
<div className="mb-2 flex !w-full items-center justify-between border-b border-neutral-500 px-6 pb-4">
|
||||
<button
|
||||
<Button
|
||||
intent="tertiary"
|
||||
size="compacted"
|
||||
onClick={decreaseMonth}
|
||||
disabled={prevMonthButtonDisabled}
|
||||
className={chevronClasses.chevronRoot}
|
||||
@@ -34,9 +38,10 @@ export const DatePickerCustomHeader = ({
|
||||
prevMonthButtonDisabled && chevronClasses.chevronClassDisabled,
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</Button>
|
||||
{format(date, "LLLL yyyy", { locale: locale === "fr" ? fr : enGB })}
|
||||
<button
|
||||
<Button
|
||||
intent="tertiary"
|
||||
onClick={increaseMonth}
|
||||
disabled={nextMonthButtonDisabled}
|
||||
className={chevronClasses.chevronRoot}
|
||||
@@ -47,7 +52,7 @@ export const DatePickerCustomHeader = ({
|
||||
nextMonthButtonDisabled && chevronClasses.chevronClassDisabled,
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { VariantProps, cva } from "class-variance-authority";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export const inputVariants = cva(
|
||||
[
|
||||
"flex w-full content-center border",
|
||||
"transition-all duration-200",
|
||||
"border-gray-300 bg-gray-50 text-gray-900 shadow-sm focus:border-primary-500 focus:ring-primary-500",
|
||||
"dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-primary-500 dark:focus:ring-primary-500",
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
state: {
|
||||
disabled: ["dark:bg-gray-50"],
|
||||
|
||||
error: ["!border-orange-700 focus:!border-orange-700"],
|
||||
},
|
||||
size: {
|
||||
small: ["font-medium", "px-3 py-2", "rounded-lg"],
|
||||
medium: ["font-medium", "p-3", "rounded-lg"],
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: "medium",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export type InputVariants = VariantProps<typeof inputVariants>;
|
||||
export type InputProps = Omit<React.ComponentProps<"input">, "size"> &
|
||||
Omit<InputVariants, "state"> & {
|
||||
hasError?: boolean;
|
||||
startIcon?: React.ReactNode;
|
||||
endIcon?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const Input = ({
|
||||
className,
|
||||
hasError,
|
||||
startIcon,
|
||||
endIcon,
|
||||
|
||||
size,
|
||||
|
||||
disabled,
|
||||
required,
|
||||
...rest
|
||||
}: InputProps) => {
|
||||
return (
|
||||
<div className="relative">
|
||||
{startIcon && (
|
||||
<div className="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-900 dark:text-white">
|
||||
{startIcon}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<input
|
||||
aria-required={required}
|
||||
aria-invalid={hasError}
|
||||
disabled={disabled}
|
||||
className={twMerge(
|
||||
inputVariants({
|
||||
size,
|
||||
state: hasError ? "error" : disabled ? "disabled" : undefined,
|
||||
}),
|
||||
startIcon && "pl-10",
|
||||
endIcon && "pr-10",
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
|
||||
{endIcon && (
|
||||
<div className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-900 dark:text-white">
|
||||
{endIcon}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -13,49 +13,57 @@ export type BaseOption<T = string, D = unknown> = {
|
||||
data?: D;
|
||||
};
|
||||
|
||||
export const classNames = <Option, IsMulti extends boolean = false>(
|
||||
hasError: boolean,
|
||||
separators: boolean,
|
||||
): ClassNamesConfig<Option, IsMulti, GroupBase<Option>> => ({
|
||||
container: () => "w-full",
|
||||
valueContainer: () => "text-gray-900 dark:text-white",
|
||||
indicatorsContainer: () => "flex items-center self-stretch shrink-0",
|
||||
clearIndicator: () => "flex items-center pr-2 text-gray-900 dark:text-white",
|
||||
dropdownIndicator: () =>
|
||||
"pointer-events-none flex items-center pr-2 text-gray-900 dark:text-white",
|
||||
indicatorSeparator: () => "w-px text-gray-900 dark:text-white",
|
||||
control: (state) =>
|
||||
twMerge(
|
||||
"group flex w-full items-center justify-between rounded-lg border p-2.5",
|
||||
"border-gray-300 bg-gray-50 text-gray-900 focus-within:border-primary-500 focus-within:ring-primary-500",
|
||||
"dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus-within:border-primary-500 dark:focus-within:ring-primary-500",
|
||||
export const createSelectClassNamesFn =
|
||||
<Option, IsMulti extends boolean = false>({
|
||||
size,
|
||||
}: {
|
||||
size?: "small" | "medium";
|
||||
}) =>
|
||||
(
|
||||
hasError: boolean,
|
||||
separators: boolean,
|
||||
): ClassNamesConfig<Option, IsMulti, GroupBase<Option>> => ({
|
||||
container: () => "w-full",
|
||||
valueContainer: () => "text-gray-900 dark:text-white",
|
||||
indicatorsContainer: () => "flex items-center self-stretch shrink-0",
|
||||
clearIndicator: () =>
|
||||
"flex items-center pr-2 text-gray-900 dark:text-white",
|
||||
dropdownIndicator: () =>
|
||||
"pointer-events-none flex items-center pr-2 text-gray-900 dark:text-white",
|
||||
indicatorSeparator: () => "w-px text-gray-900 dark:text-white",
|
||||
control: (state) =>
|
||||
twMerge(
|
||||
"group flex w-full items-center justify-between rounded-lg border p-3",
|
||||
"border-gray-300 bg-gray-50 text-gray-900 focus-within:border-primary-500 focus-within:ring-primary-500",
|
||||
"dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus-within:border-primary-500 dark:focus-within:ring-primary-500",
|
||||
|
||||
hasError && "ring-1 ring-error",
|
||||
state.isDisabled && "cursor-not-allowed",
|
||||
state.isFocused && "border-primary ring-1 ring-primary ring-opacity-50",
|
||||
),
|
||||
multiValue: () => "bg-fieldGray border rounded-lg flex space-x-1 pl-1 m-1",
|
||||
multiValueLabel: () => "",
|
||||
multiValueRemove: () => "items-center px-1 hover:text-primary",
|
||||
placeholder: () =>
|
||||
"block truncate pr-2 placeholder-gray dark:placeholder-gray-400",
|
||||
menu: () =>
|
||||
twMerge(
|
||||
"!z-30 mt-2 rounded-lg border border-gray-200 bg-white p-1",
|
||||
"border-gray-300 bg-gray-50 text-gray-900",
|
||||
"dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
),
|
||||
groupHeading: () => "mx-3 mt-2 mb-1 text-textGray text-sm uppercase",
|
||||
option: ({ isFocused, isDisabled }) =>
|
||||
twMerge(
|
||||
"px-3 py-2 hover:cursor-pointer",
|
||||
separators && "border-b border-gray-200",
|
||||
isDisabled && "opacity-50",
|
||||
isFocused && "hover:bg-primary-500 hover:text-white",
|
||||
),
|
||||
noOptionsMessage: () =>
|
||||
"text-textGray p-2 bg-gray-50 border border-dashed border-gray-200 rounded-sm",
|
||||
});
|
||||
hasError && "ring-1 ring-error",
|
||||
state.isDisabled && "cursor-not-allowed",
|
||||
state.isFocused && "border-primary ring-1 ring-primary ring-opacity-50",
|
||||
size === "small" && "p-2",
|
||||
),
|
||||
multiValue: () => "bg-fieldGray border rounded-lg flex space-x-1 pl-1 m-1",
|
||||
multiValueLabel: () => "",
|
||||
multiValueRemove: () => "items-center px-1 hover:text-primary",
|
||||
placeholder: () =>
|
||||
"block truncate pr-2 placeholder-gray dark:placeholder-gray-400",
|
||||
menu: () =>
|
||||
twMerge(
|
||||
"!z-30 mt-2 rounded-lg border border-gray-200 bg-white p-1",
|
||||
"border-gray-300 bg-gray-50 text-gray-900",
|
||||
"dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
),
|
||||
groupHeading: () => "mx-3 mt-2 mb-1 text-textGray text-sm uppercase",
|
||||
option: ({ isFocused, isDisabled }) =>
|
||||
twMerge(
|
||||
"px-3 py-2 hover:cursor-pointer",
|
||||
separators && "border-b border-gray-200",
|
||||
isDisabled && "opacity-50",
|
||||
isFocused && "hover:bg-primary-500 hover:text-white",
|
||||
),
|
||||
noOptionsMessage: () =>
|
||||
"text-textGray p-2 bg-gray-50 border border-dashed border-gray-200 rounded-sm",
|
||||
});
|
||||
|
||||
export type SelectProps<
|
||||
IsMulti extends boolean = false,
|
||||
@@ -66,6 +74,7 @@ export type SelectProps<
|
||||
required?: boolean;
|
||||
separators?: boolean;
|
||||
hasError?: boolean;
|
||||
size?: "small" | "medium";
|
||||
classNameOverrides?: ClassNamesConfig<
|
||||
BaseOption<T, D>,
|
||||
IsMulti,
|
||||
@@ -81,6 +90,7 @@ export const Select = <
|
||||
>({
|
||||
className,
|
||||
required,
|
||||
size,
|
||||
|
||||
placeholder,
|
||||
separators,
|
||||
@@ -90,6 +100,11 @@ export const Select = <
|
||||
...selectProps
|
||||
}: SelectProps<IsMulti, T, D>) => {
|
||||
const t = useTranslations("App");
|
||||
|
||||
const classNames = createSelectClassNamesFn<BaseOption<T, D>, IsMulti>({
|
||||
size,
|
||||
});
|
||||
|
||||
return (
|
||||
<ReactSelect
|
||||
options={options}
|
||||
@@ -105,10 +120,7 @@ export const Select = <
|
||||
}),
|
||||
}}
|
||||
classNames={{
|
||||
...classNames<BaseOption<T, D>, IsMulti>(
|
||||
hasError ?? false,
|
||||
separators ?? false,
|
||||
),
|
||||
...classNames(hasError ?? false, separators ?? false),
|
||||
...(classNameOverrides ?? {}),
|
||||
}}
|
||||
{...selectProps}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { twMerge } from "tailwind-merge";
|
||||
import { Prettify } from "@/types";
|
||||
|
||||
import { Field, GenericFieldProps } from "./Field";
|
||||
import { inputVariants } from "./Input";
|
||||
|
||||
type TextAreaFieldProps<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
@@ -72,33 +73,25 @@ export const TextAreaField = <
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<div className="relative">
|
||||
{startIcon && (
|
||||
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
{startIcon}
|
||||
</div>
|
||||
<textarea
|
||||
{...form.register(name, {
|
||||
required,
|
||||
})}
|
||||
aria-required={required}
|
||||
aria-invalid={hasError}
|
||||
disabled={disabled}
|
||||
className={twMerge(
|
||||
inputVariants({
|
||||
state: hasError ? "error" : disabled ? "disabled" : undefined,
|
||||
}),
|
||||
className,
|
||||
)}
|
||||
<textarea
|
||||
{...form.register(name, {
|
||||
required,
|
||||
})}
|
||||
aria-required={required}
|
||||
aria-invalid={hasError}
|
||||
disabled={disabled}
|
||||
className={twMerge(
|
||||
"flex w-full content-center rounded-lg border p-3",
|
||||
"border-gray-300 bg-gray-50 text-gray-900 shadow-sm focus:border-primary-500 focus:ring-primary-500",
|
||||
"dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-primary-500 dark:focus:ring-primary-500",
|
||||
hasError && "!border-orange-700 focus:!border-orange-700",
|
||||
disabled && "dark:bg-gray-50",
|
||||
)}
|
||||
{...rest}
|
||||
onChange={(e) => {
|
||||
field.onChange(e);
|
||||
}}
|
||||
value={field.value}
|
||||
/>
|
||||
</div>
|
||||
{...rest}
|
||||
onChange={(e) => {
|
||||
field.onChange(e);
|
||||
}}
|
||||
value={field.value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -12,13 +12,14 @@ import { twMerge } from "tailwind-merge";
|
||||
import { Prettify } from "@/types";
|
||||
|
||||
import { Field, GenericFieldProps } from "./Field";
|
||||
import { Input, InputProps } from "./Input";
|
||||
|
||||
type TextFieldProps<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = Prettify<
|
||||
GenericFieldProps<TFieldValues, TFieldName> &
|
||||
Omit<React.HTMLProps<HTMLInputElement>, "ref" | "name"> & {
|
||||
Omit<InputProps, "ref" | "name" | "size"> & {
|
||||
handleChanged?: (props: { name: string }) => void;
|
||||
startIcon?: React.ReactNode;
|
||||
}
|
||||
@@ -89,39 +90,26 @@ export const TextField = <
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<div className="relative">
|
||||
{startIcon && (
|
||||
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
{startIcon}
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
type={type}
|
||||
{...form.register(name, {
|
||||
valueAsNumber: type === "number",
|
||||
required,
|
||||
})}
|
||||
aria-required={required}
|
||||
aria-invalid={hasError}
|
||||
disabled={disabled}
|
||||
className={twMerge(
|
||||
"flex w-full content-center rounded-lg border p-3",
|
||||
"border-gray-300 bg-gray-50 text-gray-900 shadow-sm focus:border-primary-500 focus:ring-primary-500",
|
||||
"dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-primary-500 dark:focus:ring-primary-500",
|
||||
hasError && "!border-orange-700 focus:!border-orange-700",
|
||||
disabled && "dark:bg-gray-50",
|
||||
startIcon && "pl-10",
|
||||
)}
|
||||
{...rest}
|
||||
onChange={(e) => {
|
||||
field.onChange(output(e));
|
||||
}}
|
||||
value={input(field.value)}
|
||||
onBlur={() => {
|
||||
if (handleChanged) handleChanged({ name });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
type={type}
|
||||
{...form.register(name, {
|
||||
valueAsNumber: type === "number",
|
||||
required,
|
||||
})}
|
||||
hasError={hasError}
|
||||
required={required}
|
||||
disabled={disabled}
|
||||
startIcon={startIcon}
|
||||
className={twMerge(startIcon && "pl-10")}
|
||||
{...rest}
|
||||
onChange={(e) => {
|
||||
field.onChange(output(e));
|
||||
}}
|
||||
value={input(field.value)}
|
||||
onBlur={() => {
|
||||
if (handleChanged) handleChanged({ name });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"signIn": "Se connecter",
|
||||
"signOut": "Se déconnecter",
|
||||
"myZones": "Vos Régions",
|
||||
"deleteAccount": "Supprimer le compte"
|
||||
"deleteAccount": "Supprimer mon compte"
|
||||
},
|
||||
|
||||
"Footer": {
|
||||
@@ -263,9 +263,9 @@
|
||||
},
|
||||
|
||||
"DeleteAccount": {
|
||||
"title": "Supprimer le compte",
|
||||
"title": "Supprimer mon compte",
|
||||
"info": "Êtes-vous sûr de vouloir supprimer votre compte\u00a0? Cette action est irréversible.",
|
||||
"deleteButton": "Supprimer le compte",
|
||||
"deleteButton": "Supprimer mon compte",
|
||||
"success": "Votre compte a été supprimé avec succès.",
|
||||
"failure": "Oups, quelque chose s'est mal passé. Veuillez réessayer."
|
||||
},
|
||||
|
||||
@@ -2584,6 +2584,13 @@ chokidar@^3.5.3:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
class-variance-authority@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.0.tgz#1c3134d634d80271b1837452b06d821915954522"
|
||||
integrity sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==
|
||||
dependencies:
|
||||
clsx "2.0.0"
|
||||
|
||||
classnames@^2.2.6, classnames@^2.3.0:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
|
||||
@@ -2601,6 +2608,11 @@ client-only@0.0.1:
|
||||
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
|
||||
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
|
||||
|
||||
clsx@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b"
|
||||
integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==
|
||||
|
||||
clsx@^2.0.0, clsx@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
|
||||
|
||||
Reference in New Issue
Block a user