Fix build

This commit is contained in:
Arthur Belleville 2025-03-17 21:20:05 +01:00
parent c014b95e62
commit c04a437283
No known key found for this signature in database
8 changed files with 5115 additions and 159 deletions

View file

@ -11,6 +11,8 @@
},
"devDependencies": {
"@floating-ui/react": "^0.27.4",
"@internationalized/date": "^3.7.0",
"@react-aria/i18n": "^3.12.7",
"@react-aria/toast": "^3.0.0",
"@react-stately/toast": "^3.0.0",
"@tailwindcss/container-queries": "^0.1.1",
@ -39,6 +41,8 @@
"vite": "^6.2.2"
},
"dependencies": {
"@tailwindcss/vite": "^4.0.14"
"@react-stately/calendar": "^3.7.1",
"@tailwindcss/vite": "^4.0.14",
"react-stately": "^3.36.1"
}
}

View file

@ -8,13 +8,25 @@ importers:
.:
dependencies:
'@react-stately/calendar':
specifier: ^3.7.1
version: 3.7.1(react@19.0.0)
'@tailwindcss/vite':
specifier: ^4.0.14
version: 4.0.14(vite@6.2.2(jiti@2.4.2)(lightningcss@1.29.2))
react-stately:
specifier: ^3.36.1
version: 3.36.1(react@19.0.0)
devDependencies:
'@floating-ui/react':
specifier: ^0.27.4
version: 0.27.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@internationalized/date':
specifier: ^3.7.0
version: 3.7.0
'@react-aria/i18n':
specifier: ^3.12.7
version: 3.12.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@react-aria/toast':
specifier: ^3.0.0
version: 3.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@ -24,9 +36,6 @@ importers:
'@tailwindcss/container-queries':
specifier: ^0.1.1
version: 0.1.1(tailwindcss@4.0.14)
'@tailwindcss/postcss':
specifier: ^4.0.0
version: 4.0.14
'@types/react':
specifier: 19.0.10
version: 19.0.10
@ -99,10 +108,6 @@ importers:
packages:
'@alloc/quick-lru@5.2.0':
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
'@ampproject/remapping@2.3.0':
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
@ -1237,9 +1242,6 @@ packages:
resolution: {integrity: sha512-M8VCNyO/NBi5vJ2cRcI9u8w7Si+i76a7o1vveoGtbbjpEYJZYiyc7f2VGps/DqawO56l3tImIbq2OT/533jcrA==}
engines: {node: '>= 10'}
'@tailwindcss/postcss@4.0.14':
resolution: {integrity: sha512-+uIR6KtKhla1XeIanF27KtrfYy+PX+R679v5LxbkmEZlhQe3g8rk+wKj7Xgt++rWGRuFLGMXY80Ek8JNn+kN/g==}
'@tailwindcss/vite@4.0.14':
resolution: {integrity: sha512-y69ztPTRFy+13EPS/7dEFVl7q2Goh1pQueVO8IfGeyqSpcx/joNJXFk0lLhMgUbF0VFJotwRSb9ZY7Xoq3r26Q==}
peerDependencies:
@ -2210,8 +2212,6 @@ packages:
snapshots:
'@alloc/quick-lru@5.2.0': {}
'@ampproject/remapping@2.3.0':
dependencies:
'@jridgewell/gen-mapping': 0.3.8
@ -3721,15 +3721,6 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.0.14
'@tailwindcss/oxide-win32-x64-msvc': 4.0.14
'@tailwindcss/postcss@4.0.14':
dependencies:
'@alloc/quick-lru': 5.2.0
'@tailwindcss/node': 4.0.14
'@tailwindcss/oxide': 4.0.14
lightningcss: 1.29.2
postcss: 8.5.3
tailwindcss: 4.0.14
'@tailwindcss/vite@4.0.14(vite@6.2.2(jiti@2.4.2)(lightningcss@1.29.2))':
dependencies:
'@tailwindcss/node': 4.0.14

View file

@ -355,6 +355,17 @@ export const App = () => {
</section>
</main>
</div>
<footer className="mt-24 py-8 border-t border-slate-200 dark:border-slate-800">
<div className="container mx-auto px-4 text-center">
<p className="text-slate-600 dark:text-slate-400 text-sm">
© {new Date().getFullYear()} XTablo. Tous droits réservés.
</p>
<p className="text-slate-500 dark:text-slate-500 text-xs mt-2">
XTablo est une marque déposée. Les logos et noms de marques sont des
marques déposées de leurs propriétaires respectifs.
</p>
</div>
</footer>
<style>
{`
@keyframes slide {

View file

@ -1,4 +1,4 @@
import React from 'react';
import React from "react";
import {
Heading,
Calendar as RACCalendar,
@ -13,24 +13,24 @@ import {
useLocale,
composeRenderProps,
CalendarStateContext,
} from 'react-aria-components';
import { Button, ButtonGroup } from './button';
import { twMerge } from 'tailwind-merge';
import { ChevronLeftIcon, ChevronRightIcon } from './icons';
} from "react-aria-components";
import { Button, ButtonGroup } from "./button";
import { twMerge } from "tailwind-merge";
import { ChevronLeftIcon, ChevronRightIcon } from "./icons";
import {
CalendarDate,
getLocalTimeZone,
isToday,
} from '@internationalized/date';
import { CalendarState } from '@react-stately/calendar';
import { useDateFormatter } from '@react-aria/i18n';
import { NativeSelect, NativeSelectField } from './native-select';
import { Label } from './field';
} from "@internationalized/date";
import { CalendarState } from "@react-stately/calendar";
import { useDateFormatter } from "@react-aria/i18n";
import { NativeSelect, NativeSelectField } from "./native-select";
import { Label } from "./field";
export type YearRange = number | [yearsBefore: number, yearsAfter: number];
export interface CalendarProps<T extends DateValue>
extends Omit<RACCalendarProps<T>, 'visibleDuration'> {
extends Omit<RACCalendarProps<T>, "visibleDuration"> {
yearRange?: YearRange;
errorMessage?: string;
}
@ -44,7 +44,7 @@ export function Calendar<T extends DateValue>({
<RACCalendar
{...props}
className={composeRenderProps(props.className, (className) => {
return twMerge('px-1 py-2.5', className);
return twMerge("px-1 py-2.5", className);
})}
>
<CalendarHeader yearRange={yearRange} />
@ -59,7 +59,7 @@ export function Calendar<T extends DateValue>({
<CalendarCell
date={date}
className={composeRenderProps(
'',
"",
(
className,
{
@ -70,30 +70,30 @@ export function Calendar<T extends DateValue>({
isInvalid,
isUnavailable,
isFocusVisible,
},
}
) => {
return twMerge(
'relative flex size-10 cursor-default items-center justify-center rounded-lg text-sm outline-hidden',
"relative flex size-10 cursor-default items-center justify-center rounded-lg text-sm outline-hidden",
isToday(date, getLocalTimeZone()) &&
'bg-zinc-100 dark:bg-zinc-800',
isHovered && 'bg-zinc-100 dark:bg-zinc-800',
isPressed && 'bg-accent/90 text-white',
isDisabled && 'opacity-50',
"bg-zinc-100 dark:bg-zinc-800",
isHovered && "bg-zinc-100 dark:bg-zinc-800",
isPressed && "bg-accent/90 text-white",
isDisabled && "opacity-50",
isSelected && [
'bg-accent text-sm text-[lch(from_var(--color-accent)_calc((49.44_-_l)_*_infinity)_0_0)]',
isHovered && 'bg-accent dark:bg-accent',
"bg-accent text-sm text-[lch(from_var(--color-accent)_calc((49.44_-_l)_*_infinity)_0_0)]",
isHovered && "bg-accent dark:bg-accent",
isInvalid &&
'border-destructive bg-destructive text-white',
"border-destructive bg-destructive text-white",
],
isUnavailable &&
'text-destructive decoration-destructive line-through',
"text-destructive decoration-destructive line-through",
isFocusVisible && [
'outline-ring outline outline-2',
isSelected && 'outline-offset-1',
"outline-ring outline outline-2",
isSelected && "outline-offset-1",
],
className,
className
);
},
}
)}
/>
);
@ -117,8 +117,8 @@ export function CalendarHeader({ yearRange }: { yearRange?: YearRange }) {
return (
<header
className={twMerge(
'flex w-full items-center py-1 ps-4 pe-2',
yearRange ? 'ps-2' : 'ps-4',
"flex w-full items-center py-1 ps-4 pe-2",
yearRange ? "ps-2" : "ps-4"
)}
>
{yearRange ? (
@ -143,7 +143,7 @@ export function CalendarHeader({ yearRange }: { yearRange?: YearRange }) {
aria-label="Previous"
className="[&:not(:hover)]:text-muted/75 focus-visible:-outline-offset-2"
>
{direction === 'rtl' ? (
{direction === "rtl" ? (
<ChevronRightIcon className="sm:size-5" />
) : (
<ChevronLeftIcon className="sm:size-5" />
@ -158,7 +158,7 @@ export function CalendarHeader({ yearRange }: { yearRange?: YearRange }) {
aria-label="Next"
className="[&:not(:hover)]:text-muted/75 focus-visible:-outline-offset-2"
>
{direction === 'rtl' ? (
{direction === "rtl" ? (
<ChevronLeftIcon className="sm:size-5" />
) : (
<ChevronRightIcon className="sm:size-5" />
@ -193,7 +193,7 @@ function YearDropdown({
formatted: string;
}> = [];
const formatter = useDateFormatter({
year: 'numeric',
year: "numeric",
timeZone: state.timeZone,
});
@ -203,7 +203,7 @@ function YearDropdown({
if (yearsBefore <= 0 || yearsAfter <= 0) {
throw new Error(
'The yearRange prop must be a positive number or an array of two positive numbers.',
"The yearRange prop must be a positive number or an array of two positive numbers."
);
}
@ -242,7 +242,7 @@ function YearDropdown({
function MonthDropdown({ state }: { state: CalendarState }) {
const months: Array<string> = [];
const formatter = useDateFormatter({
month: 'long',
month: "long",
timeZone: state.timeZone,
});
@ -251,7 +251,7 @@ function MonthDropdown({ state }: { state: CalendarState }) {
// systems, such as the Hebrew, the number of months may differ
// between years.
const numMonths = state.focusedDate.calendar.getMonthsInYear(
state.focusedDate,
state.focusedDate
);
for (let i = 1; i <= numMonths; i++) {
const date = state.focusedDate.set({ month: i });

View file

@ -1,18 +1,18 @@
import { twMerge } from 'tailwind-merge';
import { TextProps } from 'react-aria-components';
import { Text } from './text';
import { Heading, HeadingProps } from './heading';
import { twMerge } from "tailwind-merge";
import { TextProps } from "react-aria-components";
import { Text } from "./text";
import { Heading, HeadingProps } from "./heading";
export function EmptyState({
className,
...props
}: React.JSX.IntrinsicElements['div']) {
}: React.JSX.IntrinsicElements["div"]) {
return (
<div
{...props}
className={twMerge(
'flex h-full w-full flex-col items-center justify-center gap-1 p-4 text-center @container',
className,
"flex h-full w-full flex-col items-center justify-center gap-1 p-4 text-center @container",
className
)}
/>
);
@ -22,17 +22,17 @@ export function EmptyStateIcon({
className,
children,
...props
}: React.JSX.IntrinsicElements['div']) {
}: React.JSX.IntrinsicElements["div"]) {
return (
<div
{...props}
className={twMerge(
'mb-2 flex max-w-32 items-center justify-center @md:max-w-40',
'[&>svg:not([class*=text-])]:text-muted [&>svg]:h-auto [&>svg]:min-w-12 [&>svg]:max-w-full',
className,
"mb-2 flex max-w-32 items-center justify-center @md:max-w-40",
"[&>svg:not([class*=text-])]:text-muted [&>svg]:h-auto [&>svg]:min-w-12 [&>svg]:max-w-full",
className
)}
>
{children}
{children}
</div>
);
}
@ -43,10 +43,11 @@ export function EmptyStateHeading({
...props
}: HeadingProps) {
return (
// @ts-ignore
<Heading
{...props}
level={level}
className={twMerge('text-balance', className)}
className={twMerge("text-balance", className)}
/>
);
}
@ -55,7 +56,7 @@ export function EmptyStateDescription({ className, ...props }: TextProps) {
return (
<Text
{...props}
className={twMerge('max-w-prose text-balance', className)}
className={twMerge("max-w-prose text-balance", className)}
/>
);
}
@ -63,13 +64,13 @@ export function EmptyStateDescription({ className, ...props }: TextProps) {
export function EmptyStateActions({
className,
...props
}: React.JSX.IntrinsicElements['div']) {
}: React.JSX.IntrinsicElements["div"]) {
return (
<div
{...props}
className={twMerge(
'mt-3 flex flex-col items-center justify-center gap-4 p-2',
className,
"mt-3 flex flex-col items-center justify-center gap-4 p-2",
className
)}
/>
);

View file

@ -1,9 +1,9 @@
import React from 'react';
import { LabelContext, TextFieldProps, type Key } from 'react-aria-components';
import { Tag, TagGroup, TagList } from './tag-group';
import { ListData } from 'react-stately';
import { Input, TextField } from './field';
import { twMerge } from 'tailwind-merge';
import React from "react";
import { LabelContext, TextFieldProps, type Key } from "react-aria-components";
import { Tag, TagGroup, TagList } from "./tag-group";
import { ListData } from "react-stately";
import { Input, TextField } from "./field";
import { twMerge } from "tailwind-merge";
interface TagItem {
id: number;
@ -22,14 +22,14 @@ function useTagInputContext() {
const context = React.useContext(TagInputContext);
if (!context) {
throw new Error('<TagInputContext.Provider> is required');
throw new Error("<TagInputContext.Provider> is required");
}
return context;
}
export interface TagInputProps
extends Omit<ContextType, 'tagGroupId'>,
extends Omit<ContextType, "tagGroupId">,
TextFieldProps {
children: React.ReactNode;
className?: string;
@ -50,7 +50,7 @@ export function TagsInputField({
name={name}
hidden
readOnly
value={list.items.map(({ name }) => name).join(',')}
value={list.items.map(({ name }) => name).join(",")}
/>
)}
</TagInputContext.Provider>
@ -63,7 +63,7 @@ export function TagsInput({
className?: string;
children?: React.ReactNode;
}) {
const [inputValue, setInputValue] = React.useState('');
const [inputValue, setInputValue] = React.useState("");
const { list, onTagAdd, onTagRemove } = useTagInputContext();
const deleteLast = React.useCallback(() => {
@ -84,12 +84,12 @@ export function TagsInput({
}, [list, onTagRemove]);
function handleKeyDown(e: React.KeyboardEvent) {
if (e.key === 'Enter' || e.key === ',' || e.key === ';') {
if (e.key === "Enter" || e.key === "," || e.key === ";") {
e.preventDefault();
addTag();
}
if (e.key === 'Backspace' && inputValue === '') {
if (e.key === "Backspace" && inputValue === "") {
deleteLast();
}
}
@ -100,21 +100,21 @@ export function TagsInput({
tagNames.forEach((tagName) => {
const formattedName = tagName
.trim()
.replace(/\s\s+/g, ' ')
.replace(/\t|\\t|\r|\\r|\n|\\n/g, '');
.replace(/\s\s+/g, " ")
.replace(/\t|\\t|\r|\\r|\n|\\n/g, "");
if (formattedName === '') {
if (formattedName === "") {
return;
}
const hasTagExists = list.items.find(
({ name }) =>
name.toLocaleLowerCase() === formattedName.toLocaleLowerCase(),
name.toLocaleLowerCase() === formattedName.toLocaleLowerCase()
);
if (!hasTagExists) {
const tag = {
id: (list.items.at(-1)?.id || 0) + 1,
id: (list.items[list.items.length - 1]?.id || 0) + 1,
name: formattedName,
};
@ -123,7 +123,7 @@ export function TagsInput({
}
});
setInputValue('');
setInputValue("");
}
function handleRemove(keys: Set<Key>) {
@ -143,15 +143,15 @@ export function TagsInput({
<TagGroup
aria-labelledby={labelId}
onRemove={handleRemove}
className={twMerge(className, 'w-full')}
className={twMerge(className, "w-full")}
data-ui="control"
>
<div
className={twMerge(
'flex min-h-9 items-center rounded-md',
'border has-[input[data-focused=true]]:border-ring',
'has-[input[data-invalid=true][data-focused=true]]:border-ring has-[input[data-invalid=true]]:border-destructive',
'has-[input[data-focused=true]]:ring-1 has-[input[data-focused=true]]:ring-ring',
"flex min-h-9 items-center rounded-md",
"border has-[input[data-focused=true]]:border-ring",
"has-[input[data-invalid=true][data-focused=true]]:border-ring has-[input[data-invalid=true]]:border-destructive",
"has-[input[data-focused=true]]:ring-1 has-[input[data-focused=true]]:ring-ring"
)}
>
<div className="inline-flex flex-1 flex-wrap items-center gap-1 px-2 py-[5px]">

View file

@ -1,25 +1,25 @@
import React from 'react';
import { createPortal } from 'react-dom';
import { useToastQueue } from '@react-stately/toast';
import type { AriaToastRegionProps, ToastAria } from '@react-aria/toast';
import type { ToastState } from '@react-stately/toast';
import { useToastRegion } from '@react-aria/toast';
import type { AriaToastProps } from '@react-aria/toast';
import { useToast } from '@react-aria/toast';
import React from "react";
import { createPortal } from "react-dom";
import { useToastQueue } from "@react-stately/toast";
import type { AriaToastRegionProps, ToastAria } from "@react-aria/toast";
import type { ToastState } from "@react-stately/toast";
import { useToastRegion } from "@react-aria/toast";
import type { AriaToastProps } from "@react-aria/toast";
import { useToast } from "@react-aria/toast";
import {
ButtonProps as AriaButtonProps,
composeRenderProps,
} from 'react-aria-components';
import { Button, ButtonProps } from '../button';
import { twMerge } from 'tailwind-merge';
import { toast, ToastConfig } from './toast-queue';
} from "react-aria-components";
import { Button, ButtonProps } from "../button";
import { twMerge } from "tailwind-merge";
import { toast, ToastConfig } from "./toast-queue";
import {
CircleCheckIcon,
CircleInfoIcon,
CircleXIcon,
OctagonAlertIcon,
XIcon,
} from '../icons';
} from "../icons";
interface ToastRegionProps extends AriaToastRegionProps {
state: ToastState<ToastConfig>;
@ -35,24 +35,24 @@ function ToastRegion({ state, ...props }: ToastRegionProps) {
const position =
state.visibleToasts[state.visibleToasts.length - 1].content.position ??
'bottom-right';
"bottom-right";
let className = 'bottom-6 right-6 anim ';
let className = "bottom-6 right-6 anim ";
switch (position) {
case 'bottom-left':
className = 'bottom-6 left-6';
case "bottom-left":
className = "bottom-6 left-6";
break;
case 'bottom-center':
className = 'bottom-6 left-1/2 -translate-x-1/2';
case "bottom-center":
className = "bottom-6 left-1/2 -translate-x-1/2";
break;
case 'top-left':
className = 'top-6 left-6 ';
case "top-left":
className = "top-6 left-6 ";
break;
case 'top-center':
className = 'top-6 left-1/2 -translate-x-1/2';
case "top-center":
className = "top-6 left-1/2 -translate-x-1/2";
break;
case 'top-right':
className = 'top-6 right-6';
case "top-right":
className = "top-6 right-6";
break;
default:
@ -64,8 +64,8 @@ function ToastRegion({ state, ...props }: ToastRegionProps) {
{...regionProps}
ref={ref}
className={twMerge(
'toast-region fixed isolate z-20 flex flex-col gap-2 outline-hidden',
className,
"toast-region fixed isolate z-20 flex flex-col gap-2 outline-hidden",
className
)}
>
{state.visibleToasts.map((toast) => (
@ -90,34 +90,37 @@ function Toast({ state, ...props }: ToastProps) {
titleProps,
closeButtonProps,
descriptionProps,
}: Omit<ToastAria, 'closeButtonProps'> & {
closeButtonProps: Omit<AriaButtonProps, 'children'>;
}: Omit<ToastAria, "closeButtonProps"> & {
closeButtonProps: Omit<AriaButtonProps, "children">;
} = useToast(props, state, ref);
let enteringClassName = '';
const position = props.toast.content.position ?? 'bottom-right';
let enteringClassName = "";
const position = props.toast.content.position ?? "bottom-right";
switch (position) {
case 'bottom-right':
case 'top-right':
case "bottom-right":
case "top-right":
enteringClassName =
props.toast.animation === 'entering'
? 'duration-200 slide-in-from-right animate-in ease-out'
: '';
// @ts-ignore
props.toast.animation === "entering"
? "duration-200 slide-in-from-right animate-in ease-out"
: "";
break;
case 'bottom-left':
case 'top-left':
case "bottom-left":
case "top-left":
enteringClassName =
props.toast.animation === 'entering'
? 'duration-200 slide-in-from-left animate-in ease-out'
: '';
// @ts-ignore
props.toast.animation === "entering"
? "duration-200 slide-in-from-left animate-in ease-out"
: "";
break;
case 'bottom-center':
case 'top-center':
case "bottom-center":
case "top-center":
enteringClassName =
props.toast.animation === 'entering'
? 'duration-200 slide-in-from-top animate-in ease-out'
: '';
// @ts-ignore
props.toast.animation === "entering"
? "duration-200 slide-in-from-top animate-in ease-out"
: "";
break;
default:
break;
@ -130,13 +133,13 @@ function Toast({ state, ...props }: ToastProps) {
{...toastProps}
ref={ref}
className={twMerge(
'relative isolate flex w-[min(85vw,360px)] space-x-1 rounded-lg shadow-xs transition',
'flex flex-1 rounded-ld bg-zinc-900 outline-hidden',
type ? 'px-2.5' : 'px-4',
'py-2.5',
"relative isolate flex w-[min(85vw,360px)] space-x-1 rounded-lg shadow-xs transition",
"flex flex-1 rounded-ld bg-zinc-900 outline-hidden",
type ? "px-2.5" : "px-4",
"py-2.5",
!props.toast.content.render &&
'border border-zinc-950 dark:border-zinc-800',
enteringClassName,
"border border-zinc-950 dark:border-zinc-800",
enteringClassName
)}
>
{props.toast.content.render ? (
@ -144,19 +147,19 @@ function Toast({ state, ...props }: ToastProps) {
) : (
<>
<div className="flex flex-1 items-center space-x-2.5 self-center">
{type === 'info' && (
{type === "info" && (
<CircleInfoIcon className="mt-1 size-5 self-start text-blue-500" />
)}
{type === 'error' && (
{type === "error" && (
<CircleXIcon className="mt-1 size-5 self-start text-destructive" />
)}
{type === 'warning' && (
{type === "warning" && (
<OctagonAlertIcon className="mt-1 size-5 self-start text-warning" />
)}
{type === 'success' && (
{type === "success" && (
<CircleCheckIcon className="mt-1 size-5 self-start text-success" />
)}
@ -166,7 +169,7 @@ function Toast({ state, ...props }: ToastProps) {
{...titleProps}
className={twMerge(
props.toast.content.description &&
'text-sm/6 font-medium text-zinc-50',
"text-sm/6 font-medium text-zinc-50"
)}
>
{props.toast.content.title}
@ -206,16 +209,13 @@ function Toast({ state, ...props }: ToastProps) {
);
}
export function ToastAction({
variant = 'unstyle',
...props
}: ButtonProps) {
export function ToastAction({ variant = "unstyle", ...props }: ButtonProps) {
return (
<Button
{...props}
variant={variant}
className={composeRenderProps(props.className, (className) => {
return twMerge('text-base/6 text-zinc-50 sm:text-sm/6', className);
return twMerge("text-base/6 text-zinc-50 sm:text-sm/6", className);
})}
/>
);

4949
ui/stats.html Normal file

File diff suppressed because one or more lines are too long