🔥(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