🔥(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:
Nathan Panchout
2026-01-25 20:35:37 +01:00
parent 578375c60f
commit 48062a9988
13 changed files with 0 additions and 444 deletions

View File

@@ -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}
/>
);
}}
/>
);
};

View File

@@ -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>
);
};

View File

@@ -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);
}
}

View File

@@ -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>
);
};

View File

@@ -1,12 +0,0 @@
.infinite-scroll {
&__loading-component {
display: flex;
justify-content: center;
padding: 10px;
}
&__trigger {
min-height: 20px;
margin-top: 10px;
}
}

View File

@@ -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>
);
};

View File

@@ -1 +0,0 @@
export { InfiniteScroll } from "./InfiniteScroll";

View File

@@ -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);
}
}
}

View File

@@ -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>
);
};

View File

@@ -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"
);
};

View File

@@ -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;
}
}

View File

@@ -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]
);
};

View File

@@ -1,6 +0,0 @@
import { useRouter } from "next/router";
export const useIsMinimalLayout = () => {
const router = useRouter();
return router.query.minimal === "true";
};