🔥(front) remove unused UI components and utilities
Remove deprecated UI components (breadcrumbs, circular-progress, infinite-scroll, info, responsive) and unused hooks (useCopyToClipboard, useLayout, RhfInput) that are no longer needed. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,49 +0,0 @@
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { Input, TextArea, TextAreaProps } from "@openfun/cunningham-react";
|
||||
import { InputProps } from "@openfun/cunningham-react";
|
||||
|
||||
export const RhfInput = (props: InputProps & { name: string }) => {
|
||||
const { control, setValue } = useFormContext();
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
name={props.name}
|
||||
render={({ field, fieldState }) => {
|
||||
return (
|
||||
<Input
|
||||
{...props}
|
||||
aria-invalid={!!fieldState.error}
|
||||
state={fieldState.error ? "error" : "default"}
|
||||
text={fieldState.error?.message}
|
||||
onBlur={field.onBlur}
|
||||
onChange={(e) => setValue(field.name, e.target.value)}
|
||||
value={field.value}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const RhfTextarea = (props: TextAreaProps & { name: string }) => {
|
||||
const { control, setValue } = useFormContext();
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
name={props.name}
|
||||
render={({ field, fieldState }) => {
|
||||
return (
|
||||
<TextArea
|
||||
{...props}
|
||||
aria-invalid={!!fieldState.error}
|
||||
state={fieldState.error ? "error" : "default"}
|
||||
text={fieldState.error?.message}
|
||||
onBlur={field.onBlur}
|
||||
onChange={(e) => setValue(field.name, e.target.value)}
|
||||
value={field.value}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,58 +0,0 @@
|
||||
import { Button } from "@openfun/cunningham-react";
|
||||
import React, { ReactElement, ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export type BreadcrumbItem = {
|
||||
content: ReactNode;
|
||||
};
|
||||
|
||||
export interface BreadcrumbsProps {
|
||||
items: BreadcrumbItem[];
|
||||
onBack?: () => void;
|
||||
displayBack?: boolean;
|
||||
}
|
||||
|
||||
export const Breadcrumbs = ({
|
||||
items,
|
||||
onBack,
|
||||
displayBack = false,
|
||||
}: BreadcrumbsProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="c__breadcrumbs">
|
||||
{displayBack && (
|
||||
<Button
|
||||
icon={<span className="material-icons">arrow_back</span>}
|
||||
color="neutral"
|
||||
variant="tertiary"
|
||||
className="mr-t"
|
||||
onClick={onBack}
|
||||
disabled={items.length <= 1}
|
||||
>
|
||||
{t("Précédent")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{items.map((item, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{index > 0 && (
|
||||
<span className="material-icons c__breadcrumbs__separator">
|
||||
chevron_right
|
||||
</span>
|
||||
)}
|
||||
{React.cloneElement(item.content as ReactElement<HTMLDivElement>, {
|
||||
className: `${
|
||||
(
|
||||
(item.content as ReactElement<HTMLDivElement>).props as {
|
||||
className?: string;
|
||||
}
|
||||
).className || ""
|
||||
} ${index === items.length - 1 ? "active" : ""}`,
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
.c__breadcrumbs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: auto;
|
||||
|
||||
&__separator {
|
||||
color: var(--c--contextuals--content--semantic--neutral--tertiary);
|
||||
}
|
||||
|
||||
> * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.c__breadcrumbs__button {
|
||||
height: 32px;
|
||||
padding: 4px;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: var(--c--contextuals--content--semantic--neutral--secondary);
|
||||
font-size: 16px;
|
||||
border-radius: 4px;
|
||||
font-weight: 400;
|
||||
font-family: var(--c--globals--font--families--base);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
background-color: var(
|
||||
--c--contextuals--background--semantic--neutral--tertiary
|
||||
);
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-weight: 600;
|
||||
color: var(--c--contextuals--content--semantic--neutral--primary);
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import { CheckIcon } from "../icon/Icon";
|
||||
|
||||
interface CircularProgressProps {
|
||||
progress: number;
|
||||
size?: number;
|
||||
strokeWidth?: number;
|
||||
primaryColor?: string;
|
||||
secondaryColor?: string;
|
||||
transitionDuration?: number;
|
||||
}
|
||||
|
||||
export const CircularProgress = ({
|
||||
progress,
|
||||
primaryColor = "#1a237e",
|
||||
secondaryColor = "#f0f0f0",
|
||||
transitionDuration = 0.3,
|
||||
}: CircularProgressProps) => {
|
||||
if (progress > 100) {
|
||||
progress = 100;
|
||||
}
|
||||
|
||||
const strokeWidth = 2;
|
||||
|
||||
// Fixed size of 24px for the component
|
||||
const fixedSize = 24;
|
||||
// Fixed size of 20px for the circle
|
||||
const circleSize = 20;
|
||||
|
||||
// Calculate the radius based on the circle size
|
||||
const radius = circleSize / 2;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
|
||||
// Calculate the dash offset based on progress
|
||||
const dashOffset = circumference - (progress / 100) * circumference;
|
||||
|
||||
// Determine if we should show the check mark
|
||||
const isComplete = progress >= 100;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
width: `${fixedSize}px`,
|
||||
height: `${fixedSize}px`,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{!isComplete && (
|
||||
<svg
|
||||
width={fixedSize}
|
||||
height={fixedSize}
|
||||
viewBox={`0 0 ${fixedSize} ${fixedSize}`}
|
||||
style={{ transform: isComplete ? "rotate(0deg)" : "rotate(-90deg)" }}
|
||||
>
|
||||
{/* Background circle - centered in the 24x24 container */}
|
||||
<circle
|
||||
cx={fixedSize / 2}
|
||||
cy={fixedSize / 2}
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke={secondaryColor}
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
|
||||
{/* Progress circle - centered in the 24x24 container */}
|
||||
{!isComplete && (
|
||||
<circle
|
||||
cx={fixedSize / 2}
|
||||
cy={fixedSize / 2}
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke={primaryColor}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={dashOffset}
|
||||
strokeLinecap="round"
|
||||
style={{
|
||||
transition: `stroke-dashoffset ${transitionDuration}s ease-in-out`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
)}
|
||||
{/* Check mark when complete */}
|
||||
{isComplete && <CheckIcon />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
.infinite-scroll {
|
||||
&__loading-component {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
&__trigger {
|
||||
min-height: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import { useEffect, useRef, useCallback, ReactNode } from "react";
|
||||
import { Loader, useCunningham } from "@openfun/cunningham-react";
|
||||
|
||||
interface InfiniteScrollProps {
|
||||
/** Whether there are more items to load */
|
||||
hasNextPage: boolean;
|
||||
/** Whether currently fetching the next page */
|
||||
isFetchingNextPage: boolean;
|
||||
/** Function to call when more items should be loaded */
|
||||
fetchNextPage: () => void;
|
||||
/** Children to render */
|
||||
children: ReactNode;
|
||||
/** Optional loading component to show at the bottom */
|
||||
loadingComponent?: ReactNode;
|
||||
/** Distance from bottom to trigger loading (in pixels) */
|
||||
rootMargin?: string;
|
||||
/** Intersection threshold (0-1) */
|
||||
threshold?: number;
|
||||
/** Additional CSS class for the container */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* InfiniteScroll component that automatically loads more content when the user
|
||||
* scrolls near the bottom of the container.
|
||||
*
|
||||
* Uses Intersection Observer API to detect when the trigger element comes into view
|
||||
* and automatically calls fetchNextPage when more content is available.
|
||||
*/
|
||||
export const InfiniteScroll = ({
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
children,
|
||||
loadingComponent,
|
||||
rootMargin = "300px",
|
||||
threshold = 1,
|
||||
className,
|
||||
}: InfiniteScrollProps) => {
|
||||
const { t: tc } = useCunningham();
|
||||
const loadMoreRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleIntersection = useCallback(
|
||||
(entries: IntersectionObserverEntry[]) => {
|
||||
const [entry] = entries;
|
||||
|
||||
if (entry.isIntersecting && hasNextPage && !isFetchingNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
},
|
||||
[hasNextPage, isFetchingNextPage, fetchNextPage]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(handleIntersection, {
|
||||
threshold,
|
||||
rootMargin,
|
||||
});
|
||||
|
||||
if (loadMoreRef.current) {
|
||||
observer.observe(loadMoreRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (loadMoreRef.current) {
|
||||
observer.unobserve(loadMoreRef.current);
|
||||
}
|
||||
};
|
||||
}, [handleIntersection, threshold, rootMargin]);
|
||||
|
||||
const defaultLoadingComponent = (
|
||||
<div className="infinite-scroll__loading-component">
|
||||
<Loader size="small" aria-label={tc("components.datagrid.loader_aria")} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{children}
|
||||
{/* Infinite scroll trigger and loading indicator */}
|
||||
<div ref={loadMoreRef} className="infinite-scroll__trigger">
|
||||
{isFetchingNextPage && (loadingComponent || defaultLoadingComponent)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { InfiniteScroll } from "./InfiniteScroll";
|
||||
@@ -1,19 +0,0 @@
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: var(--c--globals--spacings--3xs, 0.25rem) 0;
|
||||
align-items: center;
|
||||
|
||||
&__label {
|
||||
font-size: var(--c--globals--font--sizes--sm);
|
||||
color: var(--c--contextuals--content--semantic--neutral--primary);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&__right-content {
|
||||
&__string {
|
||||
font-size: var(--c--globals--font--sizes--sm);
|
||||
color: var(--c--contextuals--content--semantic--neutral--secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
type InfoRowProps = {
|
||||
label: string;
|
||||
rightContent: React.ReactNode | string;
|
||||
};
|
||||
|
||||
export const InfoRow = ({ label, rightContent }: InfoRowProps) => {
|
||||
return (
|
||||
<div className="info-row">
|
||||
<div className="info-row__label">{label}</div>
|
||||
<div
|
||||
className={clsx("info-row__right-content", {
|
||||
"info-row__right-content__string": typeof rightContent === "string",
|
||||
})}
|
||||
>
|
||||
{rightContent}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
export const ResponsiveDivs = () => {
|
||||
return (
|
||||
<>
|
||||
<div id="responsive-tablet"></div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const isTablet = () => {
|
||||
return (
|
||||
getComputedStyle(
|
||||
document.querySelector("#responsive-tablet")!
|
||||
).getPropertyValue("display") === "block"
|
||||
);
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
@use "sass:map";
|
||||
@use "@/styles/cunningham-tokens-sass" as *;
|
||||
|
||||
$tablet: map.get($themes, "default", "globals", "breakpoints", "tablet");
|
||||
|
||||
#responsive-tablet {
|
||||
display: none;
|
||||
|
||||
@media (max-width: $tablet) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import {
|
||||
addToast,
|
||||
ToasterItem,
|
||||
} from "@/features/ui/components/toaster/Toaster";
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const useClipboard = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return useCallback(
|
||||
(text: string, successMessage?: string, errorMessage?: string) => {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.then(() => {
|
||||
addToast(
|
||||
<ToasterItem>
|
||||
<span className="material-icons">check</span>
|
||||
<span>{successMessage ?? t("clipboard.success")}</span>
|
||||
</ToasterItem>
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
addToast(
|
||||
<ToasterItem type="error">
|
||||
<span className="material-icons">error</span>
|
||||
<span>{errorMessage ?? t("clipboard.error")}</span>
|
||||
</ToasterItem>
|
||||
);
|
||||
});
|
||||
},
|
||||
[t]
|
||||
);
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
export const useIsMinimalLayout = () => {
|
||||
const router = useRouter();
|
||||
return router.query.minimal === "true";
|
||||
};
|
||||
Reference in New Issue
Block a user