feat: 2 columns web for structure
This commit is contained in:
@@ -7,6 +7,7 @@ import React, {
|
||||
} from "react";
|
||||
import {
|
||||
Image,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
@@ -28,6 +29,29 @@ import CreateLyricsHeader from "./components/CreateLyricsHeader";
|
||||
|
||||
const ROW_HEIGHT = 54;
|
||||
|
||||
const PALETTE_SEGMENT_OVERRIDES = [
|
||||
{
|
||||
type: "pont",
|
||||
description:
|
||||
"Transition contrastée qui relance l’énergie avant d’entrer dans la dernière partie.",
|
||||
},
|
||||
{
|
||||
type: "interlude_melodique",
|
||||
description:
|
||||
"Respiration instrumentale qui met la mélodie à l’honneur sans paroles.",
|
||||
},
|
||||
{
|
||||
type: "final_apogee",
|
||||
description:
|
||||
"Clôture apothéose qui amplifie l’émotion pour un final impactant.",
|
||||
},
|
||||
{
|
||||
type: "fade_out",
|
||||
description:
|
||||
"Sortie progressive où le morceau disparaît doucement dans le mix.",
|
||||
},
|
||||
];
|
||||
|
||||
const getScrollY = () => (typeof window !== "undefined" ? window.scrollY : 0);
|
||||
|
||||
const randomSuffix = () => Math.random().toString(36).slice(2, 8);
|
||||
@@ -62,11 +86,19 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
||||
buildSortableItems(sanitizedBase)
|
||||
);
|
||||
const [activeItemId, setActiveItemId] = useState(null);
|
||||
const [insertPreviewIndex, setInsertPreviewIndex] = useState(null);
|
||||
const containerRef = useRef(null);
|
||||
const sortableItemsRef = useRef(sortableItems);
|
||||
const dragStateRef = useRef({
|
||||
itemId: null,
|
||||
paletteType: null,
|
||||
pointerId: null,
|
||||
containerTop: 0,
|
||||
containerRect: null,
|
||||
targetIndex: null,
|
||||
isOverContainer: false,
|
||||
});
|
||||
const globalListenersRef = useRef({ move: null, up: null });
|
||||
|
||||
const extractValues = useCallback(
|
||||
(items) =>
|
||||
@@ -84,6 +116,10 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
||||
});
|
||||
}, [sanitizedBase, extractValues]);
|
||||
|
||||
useEffect(() => {
|
||||
sortableItemsRef.current = sortableItems;
|
||||
}, [sortableItems]);
|
||||
|
||||
useEffect(() => {
|
||||
onChange?.(extractValues(sortableItems));
|
||||
}, [sortableItems, extractValues, onChange]);
|
||||
@@ -103,11 +139,18 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
||||
});
|
||||
}, [sortableItems]);
|
||||
|
||||
const optionalSegments = useMemo(
|
||||
const paletteSegments = useMemo(
|
||||
() =>
|
||||
OPTIONAL_STRUCTURE_SEGMENTS.map((type) => {
|
||||
const meta = getSegmentMeta(type);
|
||||
return { type, label: meta?.label || formatStructureLabel(type, 1) };
|
||||
const override = PALETTE_SEGMENT_OVERRIDES.find(
|
||||
(segment) => segment.type === type
|
||||
);
|
||||
return {
|
||||
type,
|
||||
label: meta?.label || formatStructureLabel(type, 1),
|
||||
description: override?.description || "",
|
||||
};
|
||||
}),
|
||||
[]
|
||||
);
|
||||
@@ -131,8 +174,8 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
||||
[extractValues]
|
||||
);
|
||||
|
||||
const handleOptionalSegmentPress = useCallback(
|
||||
(type) => {
|
||||
const addOptionalSegment = useCallback(
|
||||
(type, insertIndex = null) => {
|
||||
const meta = getSegmentMeta(type);
|
||||
setSortableItems((prev) => {
|
||||
const currentValues = extractValues(prev);
|
||||
@@ -145,14 +188,16 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
||||
});
|
||||
}
|
||||
|
||||
const allowsMultiple = meta?.allowMultiple !== false;
|
||||
if (!allowsMultiple && nextValues.includes(type)) {
|
||||
const filtered = nextValues.filter((value) => value !== type);
|
||||
const sanitized = sanitizeStructureList(filtered);
|
||||
return buildSortableItems(sanitized);
|
||||
if (meta?.allowMultiple === false) {
|
||||
nextValues = nextValues.filter((value) => value !== type);
|
||||
}
|
||||
|
||||
nextValues.push(type);
|
||||
const targetIndex =
|
||||
typeof insertIndex === "number"
|
||||
? clamp(insertIndex, 0, nextValues.length)
|
||||
: nextValues.length;
|
||||
|
||||
nextValues.splice(targetIndex, 0, type);
|
||||
const sanitized = sanitizeStructureList(nextValues);
|
||||
return buildSortableItems(sanitized);
|
||||
});
|
||||
@@ -160,10 +205,200 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
||||
[extractValues]
|
||||
);
|
||||
|
||||
const handleGrant = useCallback((itemId, event) => {
|
||||
const resetDragState = useCallback(() => {
|
||||
dragStateRef.current = {
|
||||
itemId: null,
|
||||
paletteType: null,
|
||||
pointerId: null,
|
||||
containerTop: 0,
|
||||
containerRect: null,
|
||||
targetIndex: null,
|
||||
isOverContainer: false,
|
||||
};
|
||||
}, []);
|
||||
|
||||
const cleanupPointerListeners = useCallback(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
const { move, up } = globalListenersRef.current;
|
||||
if (move) {
|
||||
window.removeEventListener("pointermove", move);
|
||||
}
|
||||
if (up) {
|
||||
window.removeEventListener("pointerup", up);
|
||||
window.removeEventListener("pointercancel", up);
|
||||
}
|
||||
globalListenersRef.current = { move: null, up: null };
|
||||
}
|
||||
if (typeof document !== "undefined") {
|
||||
document.body.style.userSelect = "";
|
||||
document.body.style.cursor = "";
|
||||
}
|
||||
resetDragState();
|
||||
}, [resetDragState]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
cleanupPointerListeners();
|
||||
};
|
||||
}, [cleanupPointerListeners]);
|
||||
|
||||
const handleGlobalPointerMove = useCallback(
|
||||
(event) => {
|
||||
const dragState = dragStateRef.current;
|
||||
if (!dragState.itemId && !dragState.paletteType) return;
|
||||
if (
|
||||
dragState.pointerId != null &&
|
||||
event?.pointerId != null &&
|
||||
dragState.pointerId !== event.pointerId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollY = getScrollY();
|
||||
const absoluteY =
|
||||
typeof event.pageY === "number"
|
||||
? event.pageY
|
||||
: typeof event.clientY === "number"
|
||||
? event.clientY + scrollY
|
||||
: scrollY;
|
||||
const pointerY = absoluteY - (dragState.containerTop || 0);
|
||||
|
||||
if (dragState.itemId) {
|
||||
setSortableItems((prev) => {
|
||||
const currentIndex = prev.findIndex(
|
||||
(it) => it.id === dragState.itemId
|
||||
);
|
||||
if (currentIndex === -1) return prev;
|
||||
const maxIndex = Math.max(prev.length - 1, 0);
|
||||
const targetIndex = clamp(
|
||||
Math.floor(pointerY / ROW_HEIGHT),
|
||||
0,
|
||||
maxIndex
|
||||
);
|
||||
if (targetIndex === currentIndex) return prev;
|
||||
return reorder(prev, currentIndex, targetIndex);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = dragState.containerRect;
|
||||
const listLength = sortableItemsRef.current.length;
|
||||
const isOver =
|
||||
rect == null
|
||||
? true
|
||||
: event.clientX >= rect.left &&
|
||||
event.clientX <= rect.right &&
|
||||
event.clientY >= rect.top &&
|
||||
event.clientY <= rect.bottom;
|
||||
dragStateRef.current.isOverContainer = isOver;
|
||||
|
||||
if (!isOver) {
|
||||
if (insertPreviewIndex !== null) {
|
||||
setInsertPreviewIndex(null);
|
||||
}
|
||||
dragStateRef.current.targetIndex = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const containerHeight = rect?.height ?? listLength * ROW_HEIGHT;
|
||||
let targetIndex = Math.floor(pointerY / ROW_HEIGHT);
|
||||
if (pointerY < 0) targetIndex = 0;
|
||||
if (pointerY > containerHeight) targetIndex = listLength;
|
||||
targetIndex = clamp(targetIndex, 0, listLength);
|
||||
|
||||
if (dragStateRef.current.targetIndex !== targetIndex) {
|
||||
dragStateRef.current.targetIndex = targetIndex;
|
||||
setInsertPreviewIndex(targetIndex);
|
||||
}
|
||||
},
|
||||
[insertPreviewIndex, setSortableItems]
|
||||
);
|
||||
|
||||
const handleGlobalPointerUp = useCallback(
|
||||
(event) => {
|
||||
const dragState = dragStateRef.current;
|
||||
if (!dragState.itemId && !dragState.paletteType) return;
|
||||
if (
|
||||
dragState.pointerId != null &&
|
||||
event?.pointerId != null &&
|
||||
dragState.pointerId !== event.pointerId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dragState.itemId) {
|
||||
setSortableItems((prev) =>
|
||||
buildSortableItems(extractValues(prev))
|
||||
);
|
||||
} else if (dragState.paletteType) {
|
||||
let isOver = dragState.isOverContainer;
|
||||
let targetIndex = dragState.targetIndex;
|
||||
const rect = dragState.containerRect;
|
||||
|
||||
if (rect && (!isOver || typeof targetIndex !== "number")) {
|
||||
const scrollY = getScrollY();
|
||||
const absoluteY =
|
||||
typeof event.pageY === "number"
|
||||
? event.pageY
|
||||
: typeof event.clientY === "number"
|
||||
? event.clientY + scrollY
|
||||
: scrollY;
|
||||
const pointerY = absoluteY - (dragState.containerTop || 0);
|
||||
const within =
|
||||
event.clientX >= rect.left &&
|
||||
event.clientX <= rect.right &&
|
||||
event.clientY >= rect.top &&
|
||||
event.clientY <= rect.bottom;
|
||||
|
||||
if (within) {
|
||||
isOver = true;
|
||||
const listLength = sortableItemsRef.current.length;
|
||||
const containerHeight = rect?.height ?? listLength * ROW_HEIGHT;
|
||||
let computedIndex = Math.floor(pointerY / ROW_HEIGHT);
|
||||
if (pointerY < 0) computedIndex = 0;
|
||||
if (pointerY > containerHeight) computedIndex = listLength;
|
||||
targetIndex = clamp(computedIndex, 0, listLength);
|
||||
}
|
||||
}
|
||||
|
||||
if (isOver && typeof targetIndex === "number") {
|
||||
addOptionalSegment(dragState.paletteType, targetIndex);
|
||||
}
|
||||
}
|
||||
|
||||
setActiveItemId(null);
|
||||
setInsertPreviewIndex(null);
|
||||
cleanupPointerListeners();
|
||||
},
|
||||
[addOptionalSegment, cleanupPointerListeners, extractValues]
|
||||
);
|
||||
|
||||
const attachGlobalListeners = useCallback(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
if (globalListenersRef.current.move) return;
|
||||
|
||||
const moveListener = (event) => handleGlobalPointerMove(event);
|
||||
const upListener = (event) => handleGlobalPointerUp(event);
|
||||
|
||||
window.addEventListener("pointermove", moveListener);
|
||||
window.addEventListener("pointerup", upListener);
|
||||
window.addEventListener("pointercancel", upListener);
|
||||
|
||||
globalListenersRef.current = {
|
||||
move: moveListener,
|
||||
up: upListener,
|
||||
};
|
||||
}, [handleGlobalPointerMove, handleGlobalPointerUp]);
|
||||
|
||||
const startItemDrag = useCallback(
|
||||
(itemId, event) => {
|
||||
const nativeEvent = event?.nativeEvent || {};
|
||||
if (typeof nativeEvent.button === "number" && nativeEvent.button !== 0)
|
||||
return;
|
||||
event?.preventDefault?.();
|
||||
|
||||
const rect = containerRef.current?.getBoundingClientRect();
|
||||
const scrollY = getScrollY();
|
||||
const nativeEvent = event.nativeEvent || {};
|
||||
const fallbackY =
|
||||
typeof nativeEvent.pageY === "number"
|
||||
? nativeEvent.pageY
|
||||
@@ -171,58 +406,101 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
||||
? nativeEvent.clientY + scrollY
|
||||
: scrollY;
|
||||
const containerTop = rect ? rect.top + scrollY : fallbackY;
|
||||
|
||||
dragStateRef.current = {
|
||||
itemId,
|
||||
paletteType: null,
|
||||
pointerId:
|
||||
typeof nativeEvent.pointerId === "number"
|
||||
? nativeEvent.pointerId
|
||||
: null,
|
||||
containerTop,
|
||||
containerRect: rect
|
||||
? {
|
||||
top: rect.top,
|
||||
bottom: rect.bottom,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
height: rect.height,
|
||||
}
|
||||
: null,
|
||||
targetIndex: null,
|
||||
isOverContainer: true,
|
||||
};
|
||||
|
||||
setActiveItemId(itemId);
|
||||
setInsertPreviewIndex(null);
|
||||
|
||||
if (typeof document !== "undefined") {
|
||||
document.body.style.userSelect = "none";
|
||||
document.body.style.cursor = "grabbing";
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleMove = useCallback((event) => {
|
||||
const { itemId, containerTop } = dragStateRef.current;
|
||||
if (!itemId) return;
|
||||
const nativeEvent = event.nativeEvent || {};
|
||||
attachGlobalListeners();
|
||||
},
|
||||
[attachGlobalListeners]
|
||||
);
|
||||
|
||||
const startPaletteDrag = useCallback(
|
||||
(type, event) => {
|
||||
const nativeEvent = event?.nativeEvent || {};
|
||||
if (typeof nativeEvent.button === "number" && nativeEvent.button !== 0)
|
||||
return;
|
||||
event?.preventDefault?.();
|
||||
|
||||
const rect = containerRef.current?.getBoundingClientRect();
|
||||
const scrollY = getScrollY();
|
||||
const absoluteY =
|
||||
const fallbackY =
|
||||
typeof nativeEvent.pageY === "number"
|
||||
? nativeEvent.pageY
|
||||
: typeof nativeEvent.clientY === "number"
|
||||
? nativeEvent.clientY + scrollY
|
||||
: scrollY;
|
||||
const pointerY = absoluteY - containerTop;
|
||||
const containerTop = rect ? rect.top + scrollY : fallbackY;
|
||||
|
||||
setSortableItems((prev) => {
|
||||
const currentIndex = prev.findIndex((it) => it.id === itemId);
|
||||
if (currentIndex === -1) return prev;
|
||||
const targetIndex = clamp(
|
||||
Math.floor(pointerY / ROW_HEIGHT),
|
||||
0,
|
||||
Math.max(prev.length - 1, 0)
|
||||
);
|
||||
if (targetIndex === currentIndex) return prev;
|
||||
return reorder(prev, currentIndex, targetIndex);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleRelease = useCallback(() => {
|
||||
setSortableItems((prev) => buildSortableItems(extractValues(prev)));
|
||||
dragStateRef.current = {
|
||||
itemId: null,
|
||||
containerTop: 0,
|
||||
};
|
||||
setActiveItemId(null);
|
||||
if (typeof document !== "undefined") {
|
||||
document.body.style.userSelect = "";
|
||||
paletteType: type,
|
||||
pointerId:
|
||||
typeof nativeEvent.pointerId === "number"
|
||||
? nativeEvent.pointerId
|
||||
: null,
|
||||
containerTop,
|
||||
containerRect: rect
|
||||
? {
|
||||
top: rect.top,
|
||||
bottom: rect.bottom,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
height: rect.height,
|
||||
}
|
||||
}, [extractValues]);
|
||||
: null,
|
||||
targetIndex: null,
|
||||
isOverContainer: false,
|
||||
};
|
||||
|
||||
setActiveItemId(`palette-${type}`);
|
||||
setInsertPreviewIndex(null);
|
||||
|
||||
if (typeof document !== "undefined") {
|
||||
document.body.style.userSelect = "none";
|
||||
document.body.style.cursor = "grabbing";
|
||||
}
|
||||
|
||||
attachGlobalListeners();
|
||||
},
|
||||
[attachGlobalListeners]
|
||||
);
|
||||
|
||||
const StructureRow = ({ item }) => {
|
||||
const baseType = item?.type || item?.value;
|
||||
const meta = getSegmentMeta(baseType);
|
||||
const canRemove = meta?.optional === true;
|
||||
const isIntroduction =
|
||||
baseType === "short_intro" ||
|
||||
baseType === "long_intro" ||
|
||||
baseType === "intro" ||
|
||||
baseType === "introduction";
|
||||
const containerStyle = StyleSheet.flatten([
|
||||
styles.rowContainer,
|
||||
activeItemId === item.id ? styles.activeItem : null,
|
||||
@@ -252,16 +530,14 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
||||
<Image source={icons.close} style={styles.removeIcon} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{!isIntroduction && (
|
||||
<View
|
||||
style={styles.handleWrapper}
|
||||
onStartShouldSetResponder={() => true}
|
||||
onResponderGrant={(event) => handleGrant(item.id, event)}
|
||||
onResponderMove={handleMove}
|
||||
onResponderRelease={handleRelease}
|
||||
onResponderTerminate={handleRelease}
|
||||
onPointerDown={(event) => startItemDrag(item.id, event)}
|
||||
>
|
||||
<Image source={icons.dragDots} style={styles.handleIcon} />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</CreateLyricsHeader>
|
||||
@@ -269,16 +545,17 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, gap: 10, marginTop: 16 }}>
|
||||
<View style={styles.container}>
|
||||
<CreateLyricsHeader
|
||||
title="Personnalise la structure de ta chanson"
|
||||
subTitle="Réorganise par glisser-déposer"
|
||||
/>
|
||||
<View style={{ flex: 1 }}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<View style={styles.columns}>
|
||||
<View style={styles.column}>
|
||||
<View style={styles.sectionTag}>
|
||||
<Text style={styles.sectionTagText}>Structure actuelle</Text>
|
||||
</View>
|
||||
@@ -288,11 +565,22 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
||||
style={styles.blurSection}
|
||||
>
|
||||
<View style={styles.listContainer} ref={containerRef}>
|
||||
{labeledItems.map((item) => (
|
||||
<StructureRow key={item.id} item={item} />
|
||||
{labeledItems.map((item, index) => (
|
||||
<React.Fragment key={item.id}>
|
||||
{insertPreviewIndex === index && (
|
||||
<View style={styles.dropPreview} />
|
||||
)}
|
||||
<StructureRow item={item} />
|
||||
</React.Fragment>
|
||||
))}
|
||||
{insertPreviewIndex != null &&
|
||||
insertPreviewIndex >= labeledItems.length && (
|
||||
<View style={styles.dropPreview} />
|
||||
)}
|
||||
</View>
|
||||
</ItemContainer>
|
||||
</View>
|
||||
<View style={[styles.column, styles.paletteColumn]}>
|
||||
<View style={[styles.sectionTag, styles.addSectionTag]}>
|
||||
<Text style={styles.sectionTagText}>Éléments à ajouter</Text>
|
||||
</View>
|
||||
@@ -301,52 +589,109 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
||||
disableKeyboardHeight
|
||||
style={styles.blurSection}
|
||||
>
|
||||
<View style={styles.optionsContainer}>
|
||||
{optionalSegments.map((segment) => {
|
||||
<View style={styles.paletteList}>
|
||||
{paletteSegments.map((segment) => {
|
||||
const count = segmentCounts[segment.type] || 0;
|
||||
const selected = count > 0;
|
||||
const isActive = activeItemId === `palette-${segment.type}`;
|
||||
const blurStyle = StyleSheet.flatten([
|
||||
styles.itemBlur,
|
||||
selected ? styles.selectedBlur : null,
|
||||
styles.paletteItemBlur,
|
||||
isActive ? styles.activeBlur : null,
|
||||
]);
|
||||
const badgeStyle = StyleSheet.flatten([
|
||||
styles.countBadge,
|
||||
selected ? styles.countBadgeActive : null,
|
||||
count > 0 ? styles.countBadgeActive : null,
|
||||
]);
|
||||
|
||||
return (
|
||||
<CreateLyricsHeader
|
||||
<View
|
||||
key={segment.type}
|
||||
style={styles.paletteCardWrapper}
|
||||
onPointerDown={(event) =>
|
||||
startPaletteDrag(segment.type, event)
|
||||
}
|
||||
>
|
||||
<CreateLyricsHeader
|
||||
tint="dark"
|
||||
intensity={40}
|
||||
showBorder={false}
|
||||
onPress={() => handleOptionalSegmentPress(segment.type)}
|
||||
containerStyle={styles.rowContainer}
|
||||
blurViewStyle={blurStyle}
|
||||
>
|
||||
<View style={styles.row}>
|
||||
<View style={styles.paletteRow}>
|
||||
<View style={styles.paletteTextWrapper}>
|
||||
<Text style={styles.rowText}>{segment.label}</Text>
|
||||
{segment.description ? (
|
||||
<Text style={styles.paletteDescription}>
|
||||
{segment.description}
|
||||
</Text>
|
||||
) : null}
|
||||
</View>
|
||||
<View style={styles.paletteActions}>
|
||||
<Pressable
|
||||
onPointerDown={(ev) => ev?.stopPropagation?.()}
|
||||
onPressIn={(ev) => ev?.stopPropagation?.()}
|
||||
onPress={(ev) => {
|
||||
ev?.stopPropagation?.();
|
||||
addOptionalSegment(segment.type);
|
||||
setInsertPreviewIndex(null);
|
||||
setActiveItemId(null);
|
||||
}}
|
||||
hitSlop={{ top: 8, right: 8, bottom: 8, left: 8 }}
|
||||
style={({ pressed }) =>
|
||||
StyleSheet.flatten([
|
||||
styles.addButton,
|
||||
pressed ? styles.addButtonPressed : null,
|
||||
])
|
||||
}
|
||||
>
|
||||
<Image source={icons.add} style={styles.addIcon} />
|
||||
</Pressable>
|
||||
<View style={badgeStyle}>
|
||||
<Text style={styles.countBadgeText}>{count}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</CreateLyricsHeader>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</ItemContainer>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomizeSongStructure;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
gap: 10,
|
||||
marginTop: 16,
|
||||
},
|
||||
scrollContent: {
|
||||
paddingBottom: 24,
|
||||
gap: 12,
|
||||
gap: 24,
|
||||
},
|
||||
columns: {
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
gap: 24,
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
column: {
|
||||
flexGrow: 1,
|
||||
flexBasis: 0,
|
||||
minWidth: 320,
|
||||
gap: 16,
|
||||
},
|
||||
paletteColumn: {
|
||||
flexBasis: 360,
|
||||
maxWidth: 420,
|
||||
},
|
||||
sectionTag: {
|
||||
alignSelf: "flex-start",
|
||||
@@ -354,10 +699,9 @@ const styles = StyleSheet.create({
|
||||
paddingVertical: 6,
|
||||
borderRadius: 999,
|
||||
backgroundColor: "rgba(255,255,255,0.12)",
|
||||
marginTop: 12,
|
||||
},
|
||||
addSectionTag: {
|
||||
marginTop: 24,
|
||||
marginTop: 0,
|
||||
},
|
||||
sectionTagText: {
|
||||
fontSize: 12,
|
||||
@@ -366,12 +710,19 @@ const styles = StyleSheet.create({
|
||||
letterSpacing: 1,
|
||||
textTransform: "uppercase",
|
||||
},
|
||||
blurSection: {
|
||||
width: "100%",
|
||||
},
|
||||
listContainer: {
|
||||
gap: 12,
|
||||
},
|
||||
blurSection: {
|
||||
marginTop: 12,
|
||||
width: "100%",
|
||||
dropPreview: {
|
||||
height: ROW_HEIGHT,
|
||||
borderRadius: 14,
|
||||
borderWidth: 1,
|
||||
borderStyle: "dashed",
|
||||
borderColor: Palette.transparentWhite,
|
||||
backgroundColor: "rgba(255,255,255,0.08)",
|
||||
},
|
||||
rowContainer: {
|
||||
borderRadius: 14,
|
||||
@@ -387,10 +738,13 @@ const styles = StyleSheet.create({
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
activeBlur: {
|
||||
backgroundColor: Palette.transparentWhite,
|
||||
paletteItemBlur: {
|
||||
alignItems: "stretch",
|
||||
justifyContent: "flex-start",
|
||||
flexDirection: "column",
|
||||
paddingVertical: 16,
|
||||
},
|
||||
selectedBlur: {
|
||||
activeBlur: {
|
||||
backgroundColor: Palette.transparentWhite,
|
||||
},
|
||||
row: {
|
||||
@@ -429,9 +783,46 @@ const styles = StyleSheet.create({
|
||||
tintColor: Palette.white,
|
||||
cursor: "grab",
|
||||
},
|
||||
optionsContainer: {
|
||||
paletteList: {
|
||||
gap: 12,
|
||||
},
|
||||
paletteCardWrapper: {
|
||||
cursor: "grab",
|
||||
},
|
||||
paletteRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
gap: 16,
|
||||
},
|
||||
paletteTextWrapper: {
|
||||
flex: 1,
|
||||
gap: 6,
|
||||
},
|
||||
paletteDescription: {
|
||||
fontSize: 14,
|
||||
lineHeight: 20,
|
||||
color: "rgba(255,255,255,0.75)",
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
},
|
||||
paletteActions: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
},
|
||||
addButton: {
|
||||
padding: 8,
|
||||
borderRadius: 999,
|
||||
backgroundColor: "rgba(255,255,255,0.08)",
|
||||
},
|
||||
addButtonPressed: {
|
||||
backgroundColor: "rgba(255,255,255,0.18)",
|
||||
},
|
||||
addIcon: {
|
||||
width: 18,
|
||||
height: 18,
|
||||
tintColor: Palette.white,
|
||||
},
|
||||
countBadge: {
|
||||
minWidth: 28,
|
||||
paddingHorizontal: 8,
|
||||
|
||||
Reference in New Issue
Block a user