fixes
This commit is contained in:
parent
411131bce6
commit
11194f9ed8
3 changed files with 431 additions and 384 deletions
|
|
@ -24,7 +24,7 @@ import {
|
|||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
} from "lucide-react";
|
||||
import axios from "axios";
|
||||
import axios, { isAxiosError } from "axios";
|
||||
import { route } from "ziggy-js";
|
||||
// * Components shadcn
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
|
@ -48,9 +48,8 @@ import { Skeleton } from "@/components/ui/skeleton";
|
|||
// * Other
|
||||
import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip";
|
||||
import InputError from "@/back/Components/InputError";
|
||||
import useTrans from "@/hooks/use-translations";
|
||||
import str from "@/hooks/use-string";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn, GetTranslation } from "@/lib/utils";
|
||||
import { AppEnv, DefaultModel, Paginator } from "@/back/types/global";
|
||||
import { TranslationField } from "@/back/types/i18n";
|
||||
|
||||
|
|
@ -111,13 +110,15 @@ interface ModelSelectProps
|
|||
* Callback function triggered when the selected values change.
|
||||
* Receives an array of the new selected values.
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onValueChange?: (value: number[]) => void;
|
||||
|
||||
/**
|
||||
* Callback function triggered when the selected values change.
|
||||
* Receives a single or array of selected Model(s).
|
||||
*/
|
||||
onModelChange?: (value: Model[]) => void;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onModelChange?: (value: DefaultModel[]) => void;
|
||||
|
||||
/** The default selected values when the component mounts. */
|
||||
defaultValues?: number[];
|
||||
|
|
@ -157,6 +158,7 @@ interface ModelSelectProps
|
|||
* If true, renders the multi-select component as a child of another component.
|
||||
* Optional, defaults to false.
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
asChild?: boolean;
|
||||
|
||||
/**
|
||||
|
|
@ -290,6 +292,7 @@ interface ModelSelectProps
|
|||
/**
|
||||
* The fonction to set the data in useForm inertia
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
setData: (name: string, value: number[] | number | null) => void;
|
||||
|
||||
/**
|
||||
|
|
@ -353,6 +356,7 @@ export interface ModelSelectRef {
|
|||
/**
|
||||
* Set selected values programmatically
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
setSelectedValues: (values: number[]) => void;
|
||||
/**
|
||||
* Clear all selected values
|
||||
|
|
@ -384,6 +388,7 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
animationConfig,
|
||||
maxCount = 3,
|
||||
modalPopover = false,
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
asChild = false,
|
||||
className,
|
||||
hideSelectAll = false,
|
||||
|
|
@ -411,7 +416,9 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
const [selectedValues, setSelectedValues] =
|
||||
useState<number[]>(defaultValues);
|
||||
|
||||
const [selectedModels, setSelectedModels] = useState<Model[]>([]);
|
||||
const [selectedModels, setSelectedModels] = useState<DefaultModel[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const [isAnimating, setIsAnimating] = useState(false);
|
||||
|
|
@ -441,8 +448,8 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [initialLoading, setInitialLoading] = useState<boolean>(true);
|
||||
const [options, setOptions] = useState<Model[]>([]);
|
||||
const [optionsMerged, setOptionsMerged] = useState<Model[]>([]);
|
||||
const [options, setOptions] = useState<DefaultModel[]>([]);
|
||||
const [optionsMerged, setOptionsMerged] = useState<DefaultModel[]>([]);
|
||||
// Custom by field parameter or predefined values (title, name or label).
|
||||
const [dynamicField, setDynamicField] = useState<string | null>(
|
||||
field ?? null,
|
||||
|
|
@ -485,16 +492,6 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get specific page.
|
||||
* @param pageNumber number
|
||||
* @return void
|
||||
*/
|
||||
const goToPage = async (pageNumber: number) => {
|
||||
if (pageNumber < 1 || pageNumber > lastPage) return;
|
||||
await getData(pageNumber);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get new options list with his target field.
|
||||
* @param pageNumber number
|
||||
|
|
@ -502,27 +499,30 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
*/
|
||||
const getData = async (pageNumber = 1) => {
|
||||
// Increment search id.
|
||||
const requestId = ++requestIdRef.current;
|
||||
requestIdRef.current += 1;
|
||||
const requestId = requestIdRef.current;
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = (await fetchOptions(pageNumber)) as {
|
||||
items: Paginator;
|
||||
field: string;
|
||||
model: Model[];
|
||||
model: DefaultModel[];
|
||||
};
|
||||
// Ignore obsolete answers.
|
||||
if (requestId !== requestIdRef.current) return;
|
||||
const dataModel: Model[] = data.model
|
||||
const dataModel: DefaultModel[] = data.model
|
||||
? Object.values(data.model)
|
||||
: [];
|
||||
const dataItems: Model[] = Array.isArray(data?.items?.data)
|
||||
const dataItems: DefaultModel[] = Array.isArray(
|
||||
data?.items?.data,
|
||||
)
|
||||
? data.items.data
|
||||
: [];
|
||||
setOptionsMerged(() => {
|
||||
const merged = [...dataModel, ...dataItems]
|
||||
// Supprime les éléments undefined ou sans id
|
||||
.filter(
|
||||
(item): item is Model =>
|
||||
(item): item is DefaultModel =>
|
||||
!!item && item.id !== undefined,
|
||||
)
|
||||
// Supprime les doublons par id
|
||||
|
|
@ -538,11 +538,14 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
setOptions(data.items.data ?? []);
|
||||
setPage(data.items.current_page);
|
||||
setLastPage(data.items.last_page ?? 1);
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
// Ignore obsolete errors.
|
||||
if (requestId !== requestIdRef.current) return;
|
||||
console.error(err);
|
||||
setErrorsAjax(err.response?.data?.errors);
|
||||
if (isAxiosError(err) && err.response?.data?.errors) {
|
||||
setErrorsAjax(err.response?.data?.errors);
|
||||
}
|
||||
|
||||
setOptions([]);
|
||||
setLastPage(1);
|
||||
} finally {
|
||||
|
|
@ -553,23 +556,42 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
setInitialLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get specific page.
|
||||
* @param pageNumber number
|
||||
* @return void
|
||||
*/
|
||||
const goToPage = async (pageNumber: number) => {
|
||||
if (pageNumber < 1 || pageNumber > lastPage) return;
|
||||
await getData(pageNumber);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Reset options list if empty search or null
|
||||
if (targetModel && searchValue.length === 0) {
|
||||
getData();
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Nothing to do if search is below 3 caracters.
|
||||
if (searchValue.length < 3) return;
|
||||
if (searchValue.length < 3) return undefined;
|
||||
|
||||
// Get updated options list with search.
|
||||
const handler = setTimeout(() => getData(), 300);
|
||||
return () => clearTimeout(handler);
|
||||
|
||||
function cleanup(): void {
|
||||
if (handler) {
|
||||
clearTimeout(handler);
|
||||
}
|
||||
}
|
||||
|
||||
return cleanup;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [searchValue, targetModel]);
|
||||
|
||||
const announce = useCallback(
|
||||
|
|
@ -591,15 +613,19 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
const selectedCountId = `${multiSelectId}-count`;
|
||||
|
||||
// Translations.
|
||||
const transDelete = useTrans("crud.actions.delete");
|
||||
const transClose = useTrans("back.close");
|
||||
const transClear = useTrans("back.clear");
|
||||
const transNoResults = useTrans("back.table_no_results");
|
||||
const transSearchOptions = useTrans("back.multi_select.search_options");
|
||||
const transSelectAll = useTrans("back.multi_select.select_all");
|
||||
const transMore = useTrans("back.multi_select.more");
|
||||
const transClosePopover = useTrans("back.multi_select.close_popover");
|
||||
const transClearList = useTrans("back.multi_select.clear_list");
|
||||
const transDelete = GetTranslation("crud.actions.delete");
|
||||
const transClose = GetTranslation("back.close");
|
||||
const transClear = GetTranslation("back.clear");
|
||||
const transNoResults = GetTranslation("back.table_no_results");
|
||||
const transSearchOptions = GetTranslation(
|
||||
"back.multi_select.search_options",
|
||||
);
|
||||
const transSelectAll = GetTranslation("back.multi_select.select_all");
|
||||
const transMore = GetTranslation("back.multi_select.more");
|
||||
const transClosePopover = GetTranslation(
|
||||
"back.multi_select.close_popover",
|
||||
);
|
||||
const transClearList = GetTranslation("back.multi_select.clear_list");
|
||||
|
||||
const prevDefaultValueRef = useRef<number[]>(defaultValues);
|
||||
|
||||
|
|
@ -618,6 +644,7 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
onValueChange(defaultValues);
|
||||
}
|
||||
setData(name, multiple ? defaultValues : defaultValues[0]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [defaultValues, onValueChange]);
|
||||
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
|
|
@ -661,6 +688,7 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
}
|
||||
},
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[resetToDefault, selectedValues, onValueChange],
|
||||
);
|
||||
|
||||
|
|
@ -669,7 +697,7 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
>("desktop");
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
if (typeof window === "undefined") return undefined;
|
||||
const handleResize = () => {
|
||||
const width = window.innerWidth;
|
||||
if (width < 640) {
|
||||
|
|
@ -776,13 +804,12 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
return "";
|
||||
};
|
||||
|
||||
const getAllOptions = useCallback((): Model[] => {
|
||||
const getAllOptions = useCallback((): DefaultModel[] => {
|
||||
if (optionsMerged.length === 0) return [];
|
||||
let allOptions: Model[];
|
||||
allOptions = optionsMerged;
|
||||
const allOptions: DefaultModel[] = optionsMerged;
|
||||
const valueSet = new Set<string>();
|
||||
const duplicates: string[] = [];
|
||||
const uniqueOptions: Model[] = [];
|
||||
const uniqueOptions: DefaultModel[] = [];
|
||||
allOptions.forEach((option) => {
|
||||
if (valueSet.has(option.id.toString())) {
|
||||
duplicates.push(option.id.toString());
|
||||
|
|
@ -813,13 +840,11 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
);
|
||||
}
|
||||
return deduplicateOptions ? uniqueOptions : allOptions;
|
||||
}, [options, deduplicateOptions]);
|
||||
}, [optionsMerged, deduplicateOptions]);
|
||||
|
||||
const getOptionByValue = useCallback(
|
||||
(id: number): Model | undefined => {
|
||||
const option = getAllOptions().find(
|
||||
(option) => option.id === id,
|
||||
);
|
||||
(id: number): DefaultModel | undefined => {
|
||||
const option = getAllOptions().find((opt) => opt.id === id);
|
||||
if (!option && process.env.NODE_ENV === "development") {
|
||||
console.warn(
|
||||
`MultiSelect: Option with value "${id}" not found in options list`,
|
||||
|
|
@ -856,7 +881,7 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
}
|
||||
};
|
||||
|
||||
const toggleOption = (option: Model) => {
|
||||
const toggleOption = (option: DefaultModel) => {
|
||||
if (disabled) return;
|
||||
// MODE SINGLE SELECT
|
||||
if (!multiple) {
|
||||
|
|
@ -1063,73 +1088,77 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
}
|
||||
}, [isPopoverOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
const selectedCount = selectedValues.length;
|
||||
const allOptions = getAllOptions();
|
||||
const totalOptions = allOptions.length;
|
||||
if (selectedCount !== prevSelectedCount.current) {
|
||||
const diff = selectedCount - prevSelectedCount.current;
|
||||
if (diff > 0) {
|
||||
const addedItems = selectedValues.slice(-diff);
|
||||
const addedLabels = addedItems
|
||||
.map(
|
||||
(value) =>
|
||||
allOptions.find((opt) => opt.id === value)?.[
|
||||
dynamicField as keyof Model
|
||||
],
|
||||
)
|
||||
.filter(Boolean);
|
||||
useEffect(
|
||||
() => {
|
||||
const selectedCount = selectedValues.length;
|
||||
const allOptions = getAllOptions();
|
||||
const totalOptions = allOptions.length;
|
||||
if (selectedCount !== prevSelectedCount.current) {
|
||||
const diff = selectedCount - prevSelectedCount.current;
|
||||
if (diff > 0) {
|
||||
const addedItems = selectedValues.slice(-diff);
|
||||
const addedLabels = addedItems
|
||||
.map(
|
||||
(value) =>
|
||||
allOptions.find(
|
||||
(opt) => opt.id === value,
|
||||
)?.[dynamicField as keyof DefaultModel],
|
||||
)
|
||||
.filter(Boolean);
|
||||
|
||||
if (addedLabels.length === 1) {
|
||||
if (addedLabels.length === 1) {
|
||||
announce(
|
||||
`${addedLabels[0]} selected. ${selectedCount} of ${totalOptions} options selected.`,
|
||||
);
|
||||
} else {
|
||||
announce(
|
||||
`${addedLabels.length} options selected. ${selectedCount} of ${totalOptions} total selected.`,
|
||||
);
|
||||
}
|
||||
} else if (diff < 0) {
|
||||
announce(
|
||||
`${addedLabels[0]} selected. ${selectedCount} of ${totalOptions} options selected.`,
|
||||
);
|
||||
} else {
|
||||
announce(
|
||||
`${addedLabels.length} options selected. ${selectedCount} of ${totalOptions} total selected.`,
|
||||
`Option removed. ${selectedCount} of ${totalOptions} options selected.`,
|
||||
);
|
||||
}
|
||||
} else if (diff < 0) {
|
||||
announce(
|
||||
`Option removed. ${selectedCount} of ${totalOptions} options selected.`,
|
||||
);
|
||||
prevSelectedCount.current = selectedCount;
|
||||
}
|
||||
prevSelectedCount.current = selectedCount;
|
||||
}
|
||||
|
||||
if (isPopoverOpen !== prevIsOpen.current) {
|
||||
if (isPopoverOpen) {
|
||||
announce(
|
||||
`Dropdown opened. ${totalOptions} options available. Use arrow keys to navigate.`,
|
||||
);
|
||||
} else {
|
||||
announce("Dropdown closed.");
|
||||
if (isPopoverOpen !== prevIsOpen.current) {
|
||||
if (isPopoverOpen) {
|
||||
announce(
|
||||
`Dropdown opened. ${totalOptions} options available. Use arrow keys to navigate.`,
|
||||
);
|
||||
} else {
|
||||
announce("Dropdown closed.");
|
||||
}
|
||||
prevIsOpen.current = isPopoverOpen;
|
||||
}
|
||||
prevIsOpen.current = isPopoverOpen;
|
||||
}
|
||||
|
||||
if (
|
||||
searchValue !== prevSearchValue.current &&
|
||||
searchValue !== undefined
|
||||
) {
|
||||
if (searchValue && isPopoverOpen) {
|
||||
const filteredCount = allOptions.length;
|
||||
if (
|
||||
searchValue !== prevSearchValue.current &&
|
||||
searchValue !== undefined
|
||||
) {
|
||||
if (searchValue && isPopoverOpen) {
|
||||
const filteredCount = allOptions.length;
|
||||
|
||||
announce(
|
||||
`${filteredCount} option${
|
||||
filteredCount === 1 ? "" : "s"
|
||||
} found for "${searchValue}"`,
|
||||
);
|
||||
announce(
|
||||
`${filteredCount} option${
|
||||
filteredCount === 1 ? "" : "s"
|
||||
} found for "${searchValue}"`,
|
||||
);
|
||||
}
|
||||
prevSearchValue.current = searchValue;
|
||||
}
|
||||
prevSearchValue.current = searchValue;
|
||||
}
|
||||
}, [
|
||||
selectedValues,
|
||||
isPopoverOpen,
|
||||
searchValue,
|
||||
announce,
|
||||
getAllOptions,
|
||||
]);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
selectedValues,
|
||||
isPopoverOpen,
|
||||
searchValue,
|
||||
announce,
|
||||
getAllOptions,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
|
|
@ -1157,10 +1186,10 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
}
|
||||
}, [minSelection, maxSelection, multiple]);
|
||||
|
||||
const getModelField = (option: Model): string => {
|
||||
const value = option[dynamicField as keyof Model] as unknown as
|
||||
| TranslationField
|
||||
| string;
|
||||
const getModelField = (option: DefaultModel): string => {
|
||||
const value = option[
|
||||
dynamicField as keyof DefaultModel
|
||||
] as unknown as TranslationField | string;
|
||||
if (typeof value === "object") return String(value[locale] ?? "");
|
||||
return String(value ?? "");
|
||||
};
|
||||
|
|
@ -1205,7 +1234,7 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
} selected: ${selectedValues
|
||||
.map((value) => {
|
||||
const option = getOptionByValue(value);
|
||||
if (!option) return;
|
||||
if (!option) return undefined;
|
||||
return getModelField(option);
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
|
@ -1214,6 +1243,7 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
ref={buttonRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...buttonProps}
|
||||
onClick={handleTogglePopover}
|
||||
disabled={disabled}
|
||||
|
|
@ -1243,225 +1273,7 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
maxWidth: `min(${widthConstraints.maxWidth}, 100%)`,
|
||||
}}
|
||||
>
|
||||
{selectedValues.length > 0 ? (
|
||||
<>
|
||||
{initialLoading ? (
|
||||
<Skeleton className="w-64 h-3 rounded-full" />
|
||||
) : (
|
||||
<div className="flex justify-between items-stretch w-full gap-2">
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-1",
|
||||
singleLine
|
||||
? "overflow-x-auto multiselect-singleline-scroll"
|
||||
: "flex-wrap",
|
||||
responsiveSettings.compactMode &&
|
||||
"gap-0.5",
|
||||
)}
|
||||
>
|
||||
{selectedValues
|
||||
.slice(
|
||||
0,
|
||||
responsiveSettings.maxCount,
|
||||
)
|
||||
.map((value) => {
|
||||
const option =
|
||||
getOptionByValue(
|
||||
value,
|
||||
);
|
||||
if (!option) {
|
||||
return null;
|
||||
}
|
||||
const badgeStyle: CSSProperties =
|
||||
{
|
||||
animationDuration: `${animation}s`,
|
||||
};
|
||||
return multiple ? (
|
||||
<Badge
|
||||
key={value}
|
||||
className={cn(
|
||||
getBadgeAnimationClass(),
|
||||
modelSelectVariants(
|
||||
{
|
||||
variant:
|
||||
multiple
|
||||
? variant
|
||||
: "ghost",
|
||||
},
|
||||
),
|
||||
responsiveSettings.compactMode &&
|
||||
"text-xs px-1.5 py-0.5",
|
||||
screenSize ===
|
||||
"mobile" &&
|
||||
"max-w-[120px] truncate",
|
||||
singleLine &&
|
||||
"flex-shrink-0 whitespace-nowrap",
|
||||
"[&>svg]:pointer-events-auto",
|
||||
)}
|
||||
style={{
|
||||
...badgeStyle,
|
||||
animationDuration: `${
|
||||
animationConfig?.duration ||
|
||||
animation
|
||||
}s`,
|
||||
animationDelay: `${animationConfig?.delay || 0}s`,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
screenSize ===
|
||||
"mobile" &&
|
||||
"truncate",
|
||||
)}
|
||||
>
|
||||
{getModelField(
|
||||
option,
|
||||
)}
|
||||
</span>
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={(
|
||||
event,
|
||||
) => {
|
||||
event.stopPropagation();
|
||||
toggleOption(
|
||||
option,
|
||||
);
|
||||
}}
|
||||
onKeyDown={(
|
||||
event,
|
||||
) => {
|
||||
if (
|
||||
event.key ===
|
||||
"Enter" ||
|
||||
event.key ===
|
||||
" "
|
||||
) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
toggleOption(
|
||||
option,
|
||||
);
|
||||
}
|
||||
}}
|
||||
aria-label={`Remove ${option[dynamicField as keyof Model]} from selection`}
|
||||
className="ml-2 h-4 w-4 cursor-pointer hover:bg-white/20 rounded-sm -m-0.5 focus:outline-none focus:ring-1 focus:ring-white/50"
|
||||
>
|
||||
<XCircleIcon
|
||||
className={cn(
|
||||
"h-3 w-3",
|
||||
responsiveSettings.compactMode &&
|
||||
"h-2.5 w-2.5",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Badge>
|
||||
) : (
|
||||
<p
|
||||
className={cn(
|
||||
"flex items-center overflow-hidden whitespace-nowrap max-w-[180px]",
|
||||
singleLine &&
|
||||
"flex-shrink-0",
|
||||
screenSize ===
|
||||
"mobile" &&
|
||||
"max-w-[120px]",
|
||||
responsiveSettings.compactMode &&
|
||||
"text-xs px-1.5 py-0.5",
|
||||
)}
|
||||
>
|
||||
<span className="truncate">
|
||||
{getModelField(
|
||||
option,
|
||||
)}
|
||||
</span>
|
||||
</p>
|
||||
);
|
||||
})
|
||||
.filter(Boolean)}
|
||||
{selectedValues.length >
|
||||
responsiveSettings.maxCount && (
|
||||
<Badge
|
||||
className={cn(
|
||||
"bg-transparent text-foreground border-foreground/1 hover:bg-transparent",
|
||||
getBadgeAnimationClass(),
|
||||
modelSelectVariants(
|
||||
{
|
||||
variant,
|
||||
},
|
||||
),
|
||||
responsiveSettings.compactMode &&
|
||||
"text-xs px-1.5 py-0.5",
|
||||
singleLine &&
|
||||
"flex-shrink-0 whitespace-nowrap",
|
||||
"[&>svg]:pointer-events-auto",
|
||||
)}
|
||||
style={{
|
||||
animationDuration: `${animationConfig?.duration || animation}s`,
|
||||
animationDelay: `${animationConfig?.delay || 0}s`,
|
||||
}}
|
||||
>
|
||||
{`+ ${
|
||||
selectedValues.length -
|
||||
responsiveSettings.maxCount
|
||||
} ${transMore}`}
|
||||
<XCircleIcon
|
||||
className={cn(
|
||||
"ml-2 h-4 w-4 cursor-pointer",
|
||||
responsiveSettings.compactMode &&
|
||||
"ml-1 h-3 w-3",
|
||||
)}
|
||||
onClick={(
|
||||
event,
|
||||
) => {
|
||||
event.stopPropagation();
|
||||
clearExtraOptions();
|
||||
}}
|
||||
/>
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
{!required && (
|
||||
<Button
|
||||
role="button"
|
||||
variant="ghost"
|
||||
tabIndex={0}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
handleClear();
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (
|
||||
event.key ===
|
||||
"Enter" ||
|
||||
event.key ===
|
||||
" "
|
||||
) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleClear();
|
||||
}
|
||||
}}
|
||||
aria-label={`Clear all ${selectedValues.length} selected options`}
|
||||
className="flex items-center justify-center h-4 w-4 p-0 me-2 cursor-pointer transition-opacity opacity-50 group-hover:opacity-60 hover:opacity-100 rounded-sm"
|
||||
>
|
||||
<XIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="flex min-h-4 h-full"
|
||||
/>
|
||||
<ChevronsUpDownIcon
|
||||
className="h-4 ms-2 cursor-pointer opacity-50 group-hover/button:opacity-60"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
{selectedValues.length === 0 && (
|
||||
<div className="flex items-center justify-between w-full mx-auto gap-2">
|
||||
<p className="text-start text-sm whitespace-pre-line">
|
||||
{placeholder}
|
||||
|
|
@ -1469,6 +1281,215 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
<ChevronsUpDownIcon className="h-4 cursor-pointer opacity-50 group-hover/button:opacity-60" />
|
||||
</div>
|
||||
)}
|
||||
{selectedValues.length > 0 &&
|
||||
(initialLoading ? (
|
||||
<Skeleton className="w-64 h-3 rounded-full" />
|
||||
) : (
|
||||
<div className="flex justify-between items-stretch w-full gap-2">
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-1",
|
||||
singleLine
|
||||
? "overflow-x-auto multiselect-singleline-scroll"
|
||||
: "flex-wrap",
|
||||
responsiveSettings.compactMode &&
|
||||
"gap-0.5",
|
||||
)}
|
||||
>
|
||||
{selectedValues
|
||||
.slice(
|
||||
0,
|
||||
responsiveSettings.maxCount,
|
||||
)
|
||||
.map((value) => {
|
||||
const option =
|
||||
getOptionByValue(value);
|
||||
if (!option) {
|
||||
return null;
|
||||
}
|
||||
const badgeStyle: CSSProperties =
|
||||
{
|
||||
animationDuration: `${animation}s`,
|
||||
};
|
||||
return multiple ? (
|
||||
<Badge
|
||||
key={value}
|
||||
className={cn(
|
||||
getBadgeAnimationClass(),
|
||||
modelSelectVariants(
|
||||
{
|
||||
variant:
|
||||
multiple
|
||||
? variant
|
||||
: "ghost",
|
||||
},
|
||||
),
|
||||
responsiveSettings.compactMode &&
|
||||
"text-xs px-1.5 py-0.5",
|
||||
screenSize ===
|
||||
"mobile" &&
|
||||
"max-w-[120px] truncate",
|
||||
singleLine &&
|
||||
"flex-shrink-0 whitespace-nowrap",
|
||||
"[&>svg]:pointer-events-auto",
|
||||
)}
|
||||
style={{
|
||||
...badgeStyle,
|
||||
animationDuration: `${
|
||||
animationConfig?.duration ||
|
||||
animation
|
||||
}s`,
|
||||
animationDelay: `${animationConfig?.delay || 0}s`,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
screenSize ===
|
||||
"mobile" &&
|
||||
"truncate",
|
||||
)}
|
||||
>
|
||||
{getModelField(
|
||||
option,
|
||||
)}
|
||||
</span>
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={(
|
||||
event,
|
||||
) => {
|
||||
event.stopPropagation();
|
||||
toggleOption(
|
||||
option,
|
||||
);
|
||||
}}
|
||||
onKeyDown={(
|
||||
event,
|
||||
) => {
|
||||
if (
|
||||
event.key ===
|
||||
"Enter" ||
|
||||
event.key ===
|
||||
" "
|
||||
) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
toggleOption(
|
||||
option,
|
||||
);
|
||||
}
|
||||
}}
|
||||
aria-label={`Remove ${option[dynamicField as keyof DefaultModel]} from selection`}
|
||||
className="ml-2 h-4 w-4 cursor-pointer hover:bg-white/20 rounded-sm -m-0.5 focus:outline-none focus:ring-1 focus:ring-white/50"
|
||||
>
|
||||
<XCircleIcon
|
||||
className={cn(
|
||||
"h-3 w-3",
|
||||
responsiveSettings.compactMode &&
|
||||
"h-2.5 w-2.5",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Badge>
|
||||
) : (
|
||||
<p
|
||||
className={cn(
|
||||
"flex items-center overflow-hidden whitespace-nowrap max-w-[180px]",
|
||||
singleLine &&
|
||||
"flex-shrink-0",
|
||||
screenSize ===
|
||||
"mobile" &&
|
||||
"max-w-[120px]",
|
||||
responsiveSettings.compactMode &&
|
||||
"text-xs px-1.5 py-0.5",
|
||||
)}
|
||||
>
|
||||
<span className="truncate">
|
||||
{getModelField(
|
||||
option,
|
||||
)}
|
||||
</span>
|
||||
</p>
|
||||
);
|
||||
})
|
||||
.filter(Boolean)}
|
||||
{selectedValues.length >
|
||||
responsiveSettings.maxCount && (
|
||||
<Badge
|
||||
className={cn(
|
||||
"bg-transparent text-foreground border-foreground/1 hover:bg-transparent",
|
||||
getBadgeAnimationClass(),
|
||||
modelSelectVariants({
|
||||
variant,
|
||||
}),
|
||||
responsiveSettings.compactMode &&
|
||||
"text-xs px-1.5 py-0.5",
|
||||
singleLine &&
|
||||
"flex-shrink-0 whitespace-nowrap",
|
||||
"[&>svg]:pointer-events-auto",
|
||||
)}
|
||||
style={{
|
||||
animationDuration: `${animationConfig?.duration || animation}s`,
|
||||
animationDelay: `${animationConfig?.delay || 0}s`,
|
||||
}}
|
||||
>
|
||||
{`+ ${
|
||||
selectedValues.length -
|
||||
responsiveSettings.maxCount
|
||||
} ${transMore}`}
|
||||
<XCircleIcon
|
||||
className={cn(
|
||||
"ml-2 h-4 w-4 cursor-pointer",
|
||||
responsiveSettings.compactMode &&
|
||||
"ml-1 h-3 w-3",
|
||||
)}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
clearExtraOptions();
|
||||
}}
|
||||
/>
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
{!required && (
|
||||
<Button
|
||||
role="button"
|
||||
variant="ghost"
|
||||
tabIndex={0}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
handleClear();
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (
|
||||
event.key ===
|
||||
"Enter" ||
|
||||
event.key === " "
|
||||
) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleClear();
|
||||
}
|
||||
}}
|
||||
aria-label={`Clear all ${selectedValues.length} selected options`}
|
||||
className="flex items-center justify-center h-4 w-4 p-0 me-2 cursor-pointer transition-opacity opacity-50 group-hover:opacity-60 hover:opacity-100 rounded-sm"
|
||||
>
|
||||
<XIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="flex min-h-4 h-full"
|
||||
/>
|
||||
<ChevronsUpDownIcon
|
||||
className="h-4 ms-2 cursor-pointer opacity-50 group-hover/button:opacity-60"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
|
|
@ -1651,14 +1672,14 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
content={
|
||||
<>
|
||||
<ChevronLeftIcon />
|
||||
{useTrans(
|
||||
{GetTranslation(
|
||||
"pagination.previous",
|
||||
)}
|
||||
</>
|
||||
}
|
||||
tooltipContent={
|
||||
<p>
|
||||
{useTrans(
|
||||
{GetTranslation(
|
||||
"pagination.specific_page",
|
||||
{
|
||||
params: {
|
||||
|
|
@ -1688,13 +1709,15 @@ export const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>(
|
|||
<BtnOptionnalLinkWithTooltip
|
||||
content={
|
||||
<>
|
||||
{useTrans("pagination.next")}
|
||||
{GetTranslation(
|
||||
"pagination.next",
|
||||
)}
|
||||
<ChevronRightIcon />
|
||||
</>
|
||||
}
|
||||
tooltipContent={
|
||||
<p>
|
||||
{useTrans(
|
||||
{GetTranslation(
|
||||
"pagination.specific_page",
|
||||
{
|
||||
params: {
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ interface MultiSelectProps
|
|||
* Callback function triggered when the selected values change.
|
||||
* Receives an array of the new selected values.
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onValueChange?: (value: string[]) => void;
|
||||
|
||||
/** The default selected values when the component mounts. */
|
||||
|
|
@ -182,6 +183,7 @@ interface MultiSelectProps
|
|||
* If true, renders the multi-select component as a child of another component.
|
||||
* Optional, defaults to false.
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
asChild?: boolean;
|
||||
|
||||
/**
|
||||
|
|
@ -315,6 +317,7 @@ interface MultiSelectProps
|
|||
/**
|
||||
* The fonction to set the data in useForm inertia
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
setData: (name: string, value: string[]) => void;
|
||||
|
||||
/**
|
||||
|
|
@ -362,6 +365,7 @@ export interface MultiSelectRef {
|
|||
/**
|
||||
* Set selected values programmatically
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
setSelectedValues: (values: string[]) => void;
|
||||
/**
|
||||
* Clear all selected values
|
||||
|
|
@ -393,6 +397,7 @@ export const MultiSelect = forwardRef<MultiSelectRef, MultiSelectProps>(
|
|||
animationConfig,
|
||||
maxCount = 3,
|
||||
modalPopover = false,
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
asChild = false,
|
||||
className,
|
||||
hideSelectAll = false,
|
||||
|
|
@ -482,6 +487,7 @@ export const MultiSelect = forwardRef<MultiSelectRef, MultiSelectProps>(
|
|||
onValueChange(defaultValue);
|
||||
}
|
||||
setData(name, defaultValue);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [defaultValue, onValueChange]);
|
||||
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
|
|
@ -525,6 +531,7 @@ export const MultiSelect = forwardRef<MultiSelectRef, MultiSelectProps>(
|
|||
}
|
||||
},
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[resetToDefault, selectedValues, onValueChange],
|
||||
);
|
||||
|
||||
|
|
@ -533,7 +540,7 @@ export const MultiSelect = forwardRef<MultiSelectRef, MultiSelectProps>(
|
|||
>("desktop");
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
if (typeof window === "undefined") return undefined;
|
||||
const handleResize = () => {
|
||||
const width = window.innerWidth;
|
||||
if (width < 640) {
|
||||
|
|
@ -686,7 +693,7 @@ export const MultiSelect = forwardRef<MultiSelectRef, MultiSelectProps>(
|
|||
const getOptionByValue = useCallback(
|
||||
(value: string): MultiSelectOption | undefined => {
|
||||
const option = getAllOptions().find(
|
||||
(option) => option.value === value,
|
||||
(opt) => opt.value === value,
|
||||
);
|
||||
if (!option && process.env.NODE_ENV === "development") {
|
||||
console.warn(
|
||||
|
|
@ -1053,6 +1060,7 @@ export const MultiSelect = forwardRef<MultiSelectRef, MultiSelectProps>(
|
|||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
ref={buttonRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
onClick={handleTogglePopover}
|
||||
disabled={disabled}
|
||||
|
|
@ -1159,6 +1167,7 @@ export const MultiSelect = forwardRef<MultiSelectRef, MultiSelectProps>(
|
|||
customStyle?.iconColor &&
|
||||
"text-current",
|
||||
)}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...(customStyle?.iconColor && {
|
||||
style: {
|
||||
color: customStyle.iconColor,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// * Packages
|
||||
import { useEffect, useState } from "react";
|
||||
import axios from "axios";
|
||||
import axios, { isAxiosError } from "axios";
|
||||
import { route } from "ziggy-js";
|
||||
import { ExternalLinkIcon, LinkIcon, TrashIcon } from "lucide-react";
|
||||
import { Editor } from "@tiptap/react";
|
||||
|
|
@ -216,6 +216,24 @@ export default function LinkButton({ editor, disabled }: LinkProps) {
|
|||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset all states and values in the component.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
const resetComponent = () => {
|
||||
setSelectedType(LinkType.external);
|
||||
setSelectedEnumValue(null);
|
||||
setSelectedModelId(null);
|
||||
setSelectedEntryUrl(null);
|
||||
setInputUrl("");
|
||||
setInputText("");
|
||||
setInputBlank(false);
|
||||
setEditingLink(false);
|
||||
setErrors({});
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add new link.
|
||||
*
|
||||
|
|
@ -286,24 +304,6 @@ export default function LinkButton({ editor, disabled }: LinkProps) {
|
|||
setTimeout(() => resetComponent(), animationTime);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset all states and values in the component.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
const resetComponent = () => {
|
||||
setSelectedType(LinkType.external);
|
||||
setSelectedEnumValue(null);
|
||||
setSelectedModelId(null);
|
||||
setSelectedEntryUrl(null);
|
||||
setInputUrl("");
|
||||
setInputText("");
|
||||
setInputBlank(false);
|
||||
setEditingLink(false);
|
||||
setErrors({});
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update selected enum and update states.
|
||||
*
|
||||
|
|
@ -332,12 +332,16 @@ export default function LinkButton({ editor, disabled }: LinkProps) {
|
|||
selectedEntry: value,
|
||||
modelName: selectedEnum?.name,
|
||||
})) as ModelAccessResponse;
|
||||
console.log(data);
|
||||
setSelectedEntryUrl(data.url);
|
||||
setModelToken(data.token);
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
console.error(err);
|
||||
setErrors((prev) => ({ ...prev, ...err.response?.data?.errors }));
|
||||
if (isAxiosError(err) && err.response?.data?.errors) {
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
...err.response?.data.errors,
|
||||
}));
|
||||
}
|
||||
} finally {
|
||||
setSelectedModelId(value);
|
||||
setLoading(false);
|
||||
|
|
@ -396,7 +400,7 @@ export default function LinkButton({ editor, disabled }: LinkProps) {
|
|||
const handler = async ({
|
||||
href,
|
||||
text,
|
||||
modelToken,
|
||||
modelToken, // eslint-disable-line no-shadow
|
||||
blank,
|
||||
}: {
|
||||
href: string;
|
||||
|
|
@ -434,12 +438,14 @@ export default function LinkButton({ editor, disabled }: LinkProps) {
|
|||
setSelectedModelId(data.modelId);
|
||||
setSelectedEntryUrl(href);
|
||||
setModelToken(modelToken);
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
console.error(err);
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
...err.response?.data?.errors,
|
||||
}));
|
||||
if (isAxiosError(err) && err.response?.data?.errors) {
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
...err.response?.data.errors,
|
||||
}));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setSelectedType(LinkType.external);
|
||||
|
|
@ -456,8 +462,17 @@ export default function LinkButton({ editor, disabled }: LinkProps) {
|
|||
return () => {
|
||||
editor.off("open-link-editor", handler);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [editor]);
|
||||
|
||||
let path = "";
|
||||
|
||||
if (selectedEntryUrl) {
|
||||
path = selectedEntryUrl.startsWith("/")
|
||||
? selectedEntryUrl.slice(1)
|
||||
: selectedEntryUrl;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
|
|
@ -523,7 +538,9 @@ export default function LinkButton({ editor, disabled }: LinkProps) {
|
|||
<EnumSelect
|
||||
key={resetState}
|
||||
name="modelName"
|
||||
label={useTrans("back.wysiwyg.link.enum")}
|
||||
label={GetTranslation(
|
||||
"back.wysiwyg.link.enum",
|
||||
)}
|
||||
placeholder="ex: page dynamique"
|
||||
selected={selectedEnumValue}
|
||||
options={enumModelsList}
|
||||
|
|
@ -543,7 +560,9 @@ export default function LinkButton({ editor, disabled }: LinkProps) {
|
|||
multiple={false}
|
||||
targetModel={selectedEnum?.name}
|
||||
field={selectedEnum?.field}
|
||||
label={useTrans("back.wysiwyg.link.entry")}
|
||||
label={GetTranslation(
|
||||
"back.wysiwyg.link.entry",
|
||||
)}
|
||||
placeholder="ex: John Doe"
|
||||
defaultValues={
|
||||
selectedModelId ? [selectedModelId] : []
|
||||
|
|
@ -569,22 +588,16 @@ export default function LinkButton({ editor, disabled }: LinkProps) {
|
|||
)
|
||||
.lowerCase()
|
||||
.value()}
|
||||
pathField={
|
||||
selectedEntryUrl
|
||||
? selectedEntryUrl.startsWith(
|
||||
"/",
|
||||
)
|
||||
? selectedEntryUrl.slice(1)
|
||||
: selectedEntryUrl
|
||||
: ""
|
||||
}
|
||||
pathField={path}
|
||||
isAvailable
|
||||
isColumn
|
||||
addLocale={false}
|
||||
/>
|
||||
) : (
|
||||
<small className="text-start">
|
||||
{useTrans("back.wysiwyg.link.link")}
|
||||
{GetTranslation(
|
||||
"back.wysiwyg.link.link",
|
||||
)}
|
||||
</small>
|
||||
)}
|
||||
</>
|
||||
|
|
@ -593,7 +606,9 @@ export default function LinkButton({ editor, disabled }: LinkProps) {
|
|||
{/* Url input. */}
|
||||
<SimpleString
|
||||
name="link-url"
|
||||
label={useTrans("back.wysiwyg.link.url")}
|
||||
label={GetTranslation(
|
||||
"back.wysiwyg.link.url",
|
||||
)}
|
||||
value={inputUrl}
|
||||
type="url"
|
||||
disabled={loading}
|
||||
|
|
@ -609,7 +624,7 @@ export default function LinkButton({ editor, disabled }: LinkProps) {
|
|||
content={
|
||||
<ExternalLinkIcon className="w-4 h-4 m-auto" />
|
||||
}
|
||||
tooltipContent={useTrans(
|
||||
tooltipContent={GetTranslation(
|
||||
"back.wysiwyg.link.access_url",
|
||||
)}
|
||||
className="cursor-pointer"
|
||||
|
|
@ -625,7 +640,7 @@ export default function LinkButton({ editor, disabled }: LinkProps) {
|
|||
{/* String input. */}
|
||||
<SimpleString
|
||||
name="link-text"
|
||||
label={useTrans("back.wysiwyg.link.text")}
|
||||
label={GetTranslation("back.wysiwyg.link.text")}
|
||||
value={inputText}
|
||||
disabled={loading}
|
||||
setData={(_, value) => handleUpdateText(value)}
|
||||
|
|
@ -633,7 +648,7 @@ export default function LinkButton({ editor, disabled }: LinkProps) {
|
|||
/>
|
||||
{/* Boolean input. */}
|
||||
<SimpleSwitch
|
||||
label={useTrans("back.wysiwyg.link.switch")}
|
||||
label={GetTranslation("back.wysiwyg.link.switch")}
|
||||
name="link-blank"
|
||||
checked={inputBlank}
|
||||
disabled={loading}
|
||||
|
|
@ -644,10 +659,10 @@ export default function LinkButton({ editor, disabled }: LinkProps) {
|
|||
<DialogFooter className="space-x-0 sm:space-x-0 gap-1.5 px-2 pb-2">
|
||||
{/* Cancel button. */}
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
content={str(useTrans("back.wysiwyg.link.cancel"))
|
||||
content={str(GetTranslation("back.wysiwyg.link.cancel"))
|
||||
.ucFirst()
|
||||
.value()}
|
||||
tooltipContent={useTrans(
|
||||
tooltipContent={GetTranslation(
|
||||
"back.wysiwyg.link.tooltip_cancel",
|
||||
)}
|
||||
className="cursor-pointer"
|
||||
|
|
@ -659,10 +674,10 @@ export default function LinkButton({ editor, disabled }: LinkProps) {
|
|||
/>
|
||||
{/* Save button. */}
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
content={str(useTrans("back.wysiwyg.link.insert"))
|
||||
content={str(GetTranslation("back.wysiwyg.link.insert"))
|
||||
.ucFirst()
|
||||
.value()}
|
||||
tooltipContent={useTrans(
|
||||
tooltipContent={GetTranslation(
|
||||
"back.wysiwyg.link.tooltip_insert",
|
||||
)}
|
||||
className="cursor-pointer"
|
||||
|
|
@ -674,7 +689,7 @@ export default function LinkButton({ editor, disabled }: LinkProps) {
|
|||
{/* Delete button. */}
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
content={<TrashIcon className="w-4 h-4 m-auto" />}
|
||||
tooltipContent={useTrans(
|
||||
tooltipContent={GetTranslation(
|
||||
"back.wysiwyg.link.tooltip_delete",
|
||||
)}
|
||||
className="cursor-pointer"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue