diff --git a/src/components/ListSelection/ListSelection.js b/src/components/ListSelection/ListSelection.js
index 0a7536f..60cd01f 100644
--- a/src/components/ListSelection/ListSelection.js
+++ b/src/components/ListSelection/ListSelection.js
@@ -1,16 +1,17 @@
import { BlurView } from "expo-blur";
-import React, { useMemo, useState } from "react";
+import React, { useEffect, useMemo, useState } from "react";
import { FlatList, Platform, SectionList, Text, View } from "react-native";
+import useLayoutType from "../../hooks/useLayoutType";
import CreateLyricsHeader from "../../screens/Writing/components/CreateLyricsHeader";
import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
+import { LinearGradient } from "../LinearGradient/LinearGradient";
+
+const BLUE_SELECTION_GRADIENT = ["#4673F9", "#7023F7"];
+
+const buildItemKey = (value, category) =>
+ category ? `${category}::${value}` : `${value}`;
-// Reusable selection list supporting single, multiple and sectioned data.
-// Variants supported:
-// - "simple": options are strings
-// - "titleDescription": options are { title, description }
-// - "emotion": options are { title, description, color } (uses gradient colors)
-// - "sectioned": options are sections [{ title, data: [] }] (single per section)
const ListSelection = ({
options = [],
variant = "simple",
@@ -27,8 +28,9 @@ const ListSelection = ({
itemOuterStyle,
itemTextStyle,
highlightColor = "#F94697",
+ disableHover = false,
}) => {
- // Internal fallback state when not provided by parent
+ // état interne si non contrôlé
const [internalSelected, setInternalSelected] = useState(
variant === "sectioned" ? {} : multiple ? [] : null
);
@@ -36,10 +38,18 @@ const ListSelection = ({
const setSelected =
setSelectedProp !== undefined ? setSelectedProp : setInternalSelected;
+ const { isWeb } = useLayoutType();
+ const [hoveredKey, setHoveredKey] = useState(null);
+
+ useEffect(() => {
+ if (disableHover && hoveredKey !== null) setHoveredKey(null);
+ }, [disableHover, hoveredKey]);
+
+ // helpers
const valueOf = useMemo(() => {
if (typeof getItemValue === "function") return getItemValue;
if (variant === "simple") return (item) => item;
- return (item) => item?.title ?? item; // default to title when present
+ return (item) => item?.title ?? item;
}, [getItemValue, variant]);
const formatValue = (item) =>
@@ -55,9 +65,7 @@ const ListSelection = ({
};
const isSelected = (val, category) => {
- if (variant === "sectioned") {
- return selected?.[category] === val;
- }
+ if (variant === "sectioned") return selected?.[category] === val;
if (multiple) {
const list = Array.isArray(selected) ? selected : [];
return list.some((v) => v === val);
@@ -67,6 +75,7 @@ const ListSelection = ({
const toggleSelect = (item, category) => {
const val = valueOf(item);
+
if (variant === "sectioned") {
const current = selected && typeof selected === "object" ? selected : {};
const next = { ...current };
@@ -79,19 +88,16 @@ const ListSelection = ({
if (multiple) {
const list = Array.isArray(selected) ? selected : [];
const exists = list.some((v) => v === val);
- if (exists) {
- setSelected(list.filter((v) => v !== val));
- } else {
- if (!maxSelection || list.length < maxSelection) {
- setSelected([...list, formatValue(item)]);
- }
- }
+ if (exists) setSelected(list.filter((v) => v !== val));
+ else if (!maxSelection || list.length < maxSelection)
+ setSelected([...list, formatValue(item)]);
} else {
- if (selected === val) setSelected(null);
+ if (extractSelectedComparable(selected) === val) setSelected(null);
else setSelected(formatValue(item));
}
};
+ // contenus élémentaires : toujours encapsuler le texte dans
const renderSimpleContent = (label) => (
- {label}
+ {typeof label === "string" ? label : String(label)}
);
@@ -118,14 +124,36 @@ const ListSelection = ({
...itemTextStyle,
}}
>
- {item?.title}{" "}
- : {item?.description}
+ {item?.title}
+ {` : ${item?.description ?? ""}`}
);
+ // applique le gradient bleu UNIQUEMENT sur web et quand sélectionné
+ const withWebBlueGradientIfSelected = (content, sel) => {
+ if (!isWeb || !sel) return content;
+ // wrap dans un View pour éviter texte brut direct sous le gradient (compat RN Web)
+ return (
+
+ {content}
+
+ );
+ };
+
+ // ============ SECTIONED ============
if (variant === "sectioned") {
- // Sectioned list: options as [{ title, data: [] }]
return (
+ setHoveredKey(itemKey)
+ : undefined
+ }
+ onMouseLeave={
+ isWeb && !disableHover ? () => setHoveredKey(null) : undefined
+ }
+ >
toggleSelect(item, cat)}
tint={sel ? "default" : "dark"}
colors={[Palette.tran, Palette.tran]}
- containerStyle={{
- borderWidth: 0,
- borderColor: "transparent",
- borderRadius: 14,
- ...itemContainerStyle,
- }}
+ showBorder={!sel}
+ disableBlur={sel && isWeb}
+ blurViewStyle={
+ sel && isWeb
+ ? { paddingHorizontal: 0, paddingVertical: 0 }
+ : undefined
+ }
+ containerStyle={headerContainerStyle}
>
- {renderSimpleContent(item)}
+ {withWebBlueGradientIfSelected(baseContent, sel)}
- {sel && (
+
+ {showHoverOutline && (
)}
+ keyExtractor={(item, idx) => `${valueOf(item)}-${idx}`}
/>
);
}
+ // ============ FLAT (simple/titleDescription/emotion) ============
return (
+ setHoveredKey(itemKey) : undefined
+ }
+ onMouseLeave={
+ isWeb && !disableHover ? () => setHoveredKey(null) : undefined
+ }
+ >
toggleSelect(item)}
gradientProps={
useEmotionColors && !sel ? { locations: [0.24, 1] } : undefined
}
- containerStyle={{
- borderWidth: 0,
- borderColor: "transparent",
- borderRadius: 14,
- ...itemContainerStyle,
- }}
+ showBorder={!sel} // mobile: bordure d’origine
+ disableBlur={sel && isWeb} // web: pas de blur si gradient
+ blurViewStyle={
+ sel && isWeb
+ ? { paddingHorizontal: 0, paddingVertical: 0 }
+ : undefined
+ }
+ containerStyle={headerContainerStyle}
>
- {variant === "simple"
- ? renderSimpleContent(item)
- : renderTitleDescriptionContent(item)}
+ {withWebBlueGradientIfSelected(baseContent, sel)}
- {sel && (
+
+ {showHoverOutline && (
- {title && (
-
- {title}
-
- )}
- {subTitle && (
-
- {subTitle}
-
- )}
- {children}
+ <>
+ {title && (
+
+ {title}
+
+ )}
+ {subTitle && (
+
+ {subTitle}
+
+ )}
+ {children}
+ >
) : (
- {title && (
-
- {title}
-
- )}
- {subTitle && (
-
- {subTitle}
-
- )}
- {children}
+ <>
+ {title && (
+
+ {title}
+
+ )}
+ {subTitle && (
+
+ {subTitle}
+
+ )}
+ {children}
+ >
)}
diff --git a/src/screens/Writing/components/CustomInput.js b/src/screens/Writing/components/CustomInput.js
index 9b18d5c..09198f8 100644
--- a/src/screens/Writing/components/CustomInput.js
+++ b/src/screens/Writing/components/CustomInput.js
@@ -1,5 +1,5 @@
-import { View, Text, TextInput } from "react-native";
import React from "react";
+import { Text, TextInput, View } from "react-native";
import { Palette } from "../../../styles";
import { FONT_FAMILY } from "../../../styles/Fonts";
@@ -17,44 +17,46 @@ const CustomInput = ({
}) => {
return (
- {label && (
-
+ {label && (
+
+ {label}
+
+ )}
+
- {label}
-
- )}
-
-
-
+
+
+ >
);
};