feat: admin auth, web menu template studio, live text editing & Ayris Tech footer backlink
This commit is contained in:
@@ -1,11 +1,9 @@
|
||||
import React, { useRef } from "react";
|
||||
import {
|
||||
PanResponder,
|
||||
Pressable,
|
||||
Text,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import * as Haptics from "expo-haptics";
|
||||
import { type CanvasTextLayer, getCanvasFontFamily } from "./types";
|
||||
|
||||
@@ -23,6 +21,78 @@ interface CanvasLayerItemProps {
|
||||
canvasHeight: number;
|
||||
}
|
||||
|
||||
export const CurvedTextNative: React.FC<{
|
||||
text: string;
|
||||
fontSize: number;
|
||||
color: string;
|
||||
fontFamily: string;
|
||||
fontWeight?: any;
|
||||
letterSpacing?: number;
|
||||
curveDegree: number;
|
||||
hasShadow?: boolean;
|
||||
}> = ({
|
||||
text,
|
||||
fontSize,
|
||||
color,
|
||||
fontFamily,
|
||||
fontWeight = "800",
|
||||
letterSpacing = 0.5,
|
||||
curveDegree,
|
||||
hasShadow,
|
||||
}) => {
|
||||
const isUp = curveDegree > 0;
|
||||
const absDegree = Math.abs(curveDegree);
|
||||
const radius = Math.max(40, 420 - absDegree * 3.5);
|
||||
const charWidth = fontSize * 0.52 + (letterSpacing || 0);
|
||||
const numChars = text.length;
|
||||
const arcLength = numChars * charWidth;
|
||||
const totalAngleRad = arcLength / radius;
|
||||
const stepRad = numChars > 1 ? totalAngleRad / (numChars - 1) : 0;
|
||||
const startAngleRad = -totalAngleRad / 2;
|
||||
|
||||
const maxOffsetY = Math.abs(-radius * (Math.cos(totalAngleRad / 2) - 1));
|
||||
const containerHeight = fontSize + maxOffsetY + 12;
|
||||
const containerWidth = Math.max(numChars * charWidth, radius * 2 * Math.sin(totalAngleRad / 2)) + 16;
|
||||
|
||||
return (
|
||||
<View style={{ width: containerWidth, height: containerHeight, alignItems: "center", justifyContent: "center" }}>
|
||||
{text.split("").map((ch, i) => {
|
||||
const angleRad = startAngleRad + i * stepRad;
|
||||
const angleDeg = angleRad * (180 / Math.PI);
|
||||
const charX = radius * Math.sin(angleRad);
|
||||
const charY = isUp
|
||||
? -radius * (Math.cos(angleRad) - 1)
|
||||
: radius * (Math.cos(angleRad) - 1);
|
||||
const charRotate = isUp ? angleDeg : -angleDeg;
|
||||
|
||||
return (
|
||||
<Text
|
||||
key={i}
|
||||
style={{
|
||||
position: "absolute",
|
||||
fontSize,
|
||||
color,
|
||||
fontFamily,
|
||||
fontWeight,
|
||||
letterSpacing,
|
||||
textShadowColor: hasShadow ? "rgba(0,0,0,0.8)" : "transparent",
|
||||
textShadowOffset: { width: 0, height: 1 },
|
||||
textShadowRadius: 3,
|
||||
transform: [
|
||||
{ translateX: charX },
|
||||
{ translateY: charY },
|
||||
{ rotate: `${charRotate}deg` },
|
||||
],
|
||||
}}
|
||||
>
|
||||
{ch === " " ? "\u00A0" : ch}
|
||||
</Text>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export const CanvasLayerItem: React.FC<CanvasLayerItemProps> = ({
|
||||
layer,
|
||||
isSelected,
|
||||
@@ -191,51 +261,35 @@ export const CanvasLayerItem: React.FC<CanvasLayerItemProps> = ({
|
||||
}}
|
||||
>
|
||||
<View style={renderBackgroundStyle()}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: layer.fontSize,
|
||||
color: layer.color,
|
||||
fontFamily: getCanvasFontFamily(layer.fontId),
|
||||
fontWeight: layer.fontWeight || "bold",
|
||||
textAlign: layer.textAlign || "center",
|
||||
letterSpacing: layer.letterSpacing ?? 0.5,
|
||||
textShadowColor: layer.hasShadow ? "rgba(0,0,0,0.8)" : "transparent",
|
||||
textShadowOffset: { width: 0, height: 1 },
|
||||
textShadowRadius: 3,
|
||||
}}
|
||||
>
|
||||
{layer.text}
|
||||
</Text>
|
||||
{layer.curveDegree && layer.curveDegree !== 0 ? (
|
||||
<CurvedTextNative
|
||||
text={layer.text}
|
||||
fontSize={layer.fontSize}
|
||||
color={layer.color}
|
||||
fontFamily={getCanvasFontFamily(layer.fontId)}
|
||||
fontWeight={layer.fontWeight || "bold"}
|
||||
letterSpacing={layer.letterSpacing ?? 0.5}
|
||||
curveDegree={layer.curveDegree}
|
||||
hasShadow={layer.hasShadow}
|
||||
/>
|
||||
) : (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: layer.fontSize,
|
||||
color: layer.color,
|
||||
fontFamily: getCanvasFontFamily(layer.fontId),
|
||||
fontWeight: layer.fontWeight || "bold",
|
||||
textAlign: layer.textAlign || "center",
|
||||
letterSpacing: layer.letterSpacing ?? 0.5,
|
||||
textShadowColor: layer.hasShadow ? "rgba(0,0,0,0.8)" : "transparent",
|
||||
textShadowOffset: { width: 0, height: 1 },
|
||||
textShadowRadius: 3,
|
||||
}}
|
||||
>
|
||||
{layer.text}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Seçili İken Üst Köşedeki Küçük Silme Rozeti */}
|
||||
{isSelected && (
|
||||
<Pressable
|
||||
onPress={(e) => {
|
||||
e.stopPropagation?.();
|
||||
try {
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
|
||||
} catch {}
|
||||
onDelete(layer.id);
|
||||
}}
|
||||
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: -8,
|
||||
right: -8,
|
||||
backgroundColor: "#EF4444",
|
||||
width: 18,
|
||||
height: 18,
|
||||
borderRadius: 9,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderWidth: 1.5,
|
||||
borderColor: "#0C0C0E",
|
||||
}}
|
||||
>
|
||||
<Ionicons name="close" size={11} color="#FFFFFF" />
|
||||
</Pressable>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -45,6 +45,7 @@ export const InstagramTextEditorModal: React.FC<InstagramTextEditorModalProps> =
|
||||
const [textAlign, setTextAlign] = useState<"left" | "center" | "right">("center");
|
||||
const [bgStyle, setBgStyle] = useState<"none" | "solid" | "soft" | "outline">("none");
|
||||
const [bgBoxColor, setBgBoxColor] = useState<string | undefined>(undefined);
|
||||
const [curveDegree, setCurveDegree] = useState<number>(0);
|
||||
|
||||
// Modal açıldığında layer verilerini yükle
|
||||
useEffect(() => {
|
||||
@@ -54,6 +55,7 @@ export const InstagramTextEditorModal: React.FC<InstagramTextEditorModalProps> =
|
||||
setColor(layer.color || "#FFFFFF");
|
||||
setFontSize(layer.fontSize || 16);
|
||||
setTextAlign(layer.textAlign || "center");
|
||||
setCurveDegree(layer.curveDegree || 0);
|
||||
if (layer.bgBoxColor) {
|
||||
if (layer.bgBoxColor.includes("0.2") || layer.bgBoxColor.includes("0.3")) {
|
||||
setBgStyle("soft");
|
||||
@@ -73,6 +75,19 @@ export const InstagramTextEditorModal: React.FC<InstagramTextEditorModalProps> =
|
||||
}
|
||||
}, [layer, visible]);
|
||||
|
||||
// Kavis Modunu Değiştir (Düz (0) -> Yukarı Kavis (40) -> Ters Kavis (-40) -> Düz)
|
||||
const handleToggleCurveDegree = () => {
|
||||
try {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
} catch {}
|
||||
setCurveDegree((prev) => {
|
||||
if (prev === 0) return 40;
|
||||
if (prev === 40) return 60;
|
||||
if (prev === 60) return -40;
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
|
||||
// Arka plan pill stilini sırayla değiştir (Instagram "A" butonu mantığı)
|
||||
const handleToggleBgStyle = () => {
|
||||
try {
|
||||
@@ -155,6 +170,7 @@ export const InstagramTextEditorModal: React.FC<InstagramTextEditorModalProps> =
|
||||
textAlign,
|
||||
bgBoxColor: finalBg,
|
||||
bgStyle,
|
||||
curveDegree,
|
||||
hasShadow: bgStyle === "outline" || (!finalBg && color !== "#121215"),
|
||||
});
|
||||
onClose();
|
||||
@@ -234,6 +250,26 @@ export const InstagramTextEditorModal: React.FC<InstagramTextEditorModalProps> =
|
||||
)}
|
||||
</Pressable>
|
||||
|
||||
{/* Kavis (Yay / Eğri Yazı) Toggle Butonu */}
|
||||
<Pressable
|
||||
onPress={handleToggleCurveDegree}
|
||||
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
||||
style={[
|
||||
styles.toolPill,
|
||||
curveDegree !== 0 && { backgroundColor: "#D4AF37" },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.bgPillText,
|
||||
{ fontSize: 14 },
|
||||
curveDegree !== 0 ? { color: "#0C0C0E", fontWeight: "900" } : { color: "#FFFFFF" },
|
||||
]}
|
||||
>
|
||||
{curveDegree > 0 ? "◠" : curveDegree < 0 ? "◡" : "⌒"}
|
||||
</Text>
|
||||
</Pressable>
|
||||
|
||||
{/* Katmanı Sil (Varsa) */}
|
||||
{onDelete && (
|
||||
<Pressable
|
||||
@@ -358,6 +394,69 @@ export const InstagramTextEditorModal: React.FC<InstagramTextEditorModalProps> =
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* ALT BÖLÜM 0: KAVİS HASSAS AYAR BARI */}
|
||||
<View style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", paddingHorizontal: 16, paddingVertical: 4, gap: 10 }}>
|
||||
<Text style={{ color: "#A1A1AA", fontSize: 11, fontWeight: "700" }}>KAVİS:</Text>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", backgroundColor: "rgba(255,255,255,0.1)", borderRadius: 14, paddingHorizontal: 6, paddingVertical: 3, gap: 8 }}>
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
try { Haptics.selectionAsync(); } catch {}
|
||||
setCurveDegree((deg) => Math.max(-100, deg - 5));
|
||||
}}
|
||||
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
||||
style={{ width: 26, height: 26, borderRadius: 13, backgroundColor: "rgba(255,255,255,0.15)", alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<Ionicons name="remove" size={15} color="#FFFFFF" />
|
||||
</Pressable>
|
||||
|
||||
<Text style={{ color: "#D4AF37", fontSize: 13, fontWeight: "900", minWidth: 36, textAlign: "center" }}>
|
||||
{curveDegree}°
|
||||
</Text>
|
||||
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
try { Haptics.selectionAsync(); } catch {}
|
||||
setCurveDegree((deg) => Math.min(100, deg + 5));
|
||||
}}
|
||||
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
||||
style={{ width: 26, height: 26, borderRadius: 13, backgroundColor: "rgba(255,255,255,0.15)", alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<Ionicons name="add" size={15} color="#FFFFFF" />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 6 }}>
|
||||
{[
|
||||
{ label: "Düz (0°)", deg: 0 },
|
||||
{ label: "25° ◠", deg: 25 },
|
||||
{ label: "45° ◠", deg: 45 },
|
||||
{ label: "65° ◠", deg: 65 },
|
||||
{ label: "-35° ◡", deg: -35 },
|
||||
].map((item) => {
|
||||
const isActive = curveDegree === item.deg;
|
||||
return (
|
||||
<Pressable
|
||||
key={item.deg}
|
||||
onPress={() => {
|
||||
try { Haptics.selectionAsync(); } catch {}
|
||||
setCurveDegree(item.deg);
|
||||
}}
|
||||
style={{
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 5,
|
||||
borderRadius: 12,
|
||||
backgroundColor: isActive ? "#D4AF37" : "rgba(255,255,255,0.1)",
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: isActive ? "#0C0C0E" : "#FFFFFF", fontSize: 11, fontWeight: isActive ? "800" : "600" }}>
|
||||
{item.label}
|
||||
</Text>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
{/* ALT BÖLÜM 1: RENK SEÇİCİ BALONCUKLAR */}
|
||||
<View style={styles.colorPickerBar}>
|
||||
<ScrollView
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
Pressable,
|
||||
ScrollView,
|
||||
Text,
|
||||
TextInput,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
@@ -14,6 +15,7 @@ import { InstagramTextEditorModal } from "./InstagramTextEditorModal";
|
||||
import {
|
||||
type CanvasTextLayer,
|
||||
type CanvasQrConfig,
|
||||
CANVAS_FONT_OPTIONS,
|
||||
QR_SIZE_PRESETS,
|
||||
QR_RADIUS_PRESETS,
|
||||
getDefaultLayersForTemplate,
|
||||
@@ -537,7 +539,79 @@ export const QrCanvasStudio: React.FC<QrCanvasStudioProps> = ({
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Alt Sıra: Hızlı Döndürme Açısı Hapları */}
|
||||
{/* Hızlı Metin Değiştirme Girdisi */}
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
backgroundColor: "#0C0C0E",
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
borderWidth: 1,
|
||||
borderColor: "rgba(212, 175, 55, 0.4)",
|
||||
}}
|
||||
>
|
||||
<Ionicons name="text-outline" size={14} color="#D4AF37" />
|
||||
<TextInput
|
||||
value={selectedLayer.text}
|
||||
onChangeText={(newText: string) => {
|
||||
onLayersChange((prev) =>
|
||||
prev.map((l) => (l.id === selectedLayer.id ? { ...l, text: newText } : l))
|
||||
);
|
||||
}}
|
||||
placeholder="Metin yazın..."
|
||||
placeholderTextColor="#71717A"
|
||||
style={{
|
||||
flex: 1,
|
||||
color: "#FFFFFF",
|
||||
fontSize: 13,
|
||||
fontWeight: "700",
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Alt Sıra 1: Font Stili Seçici */}
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
|
||||
<Text style={{ color: "#71717A", fontSize: 10, fontWeight: "700" }}>FONT:</Text>
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 6 }}>
|
||||
{CANVAS_FONT_OPTIONS.map((f) => {
|
||||
const isActive = (selectedLayer.fontId || "sans") === f.id;
|
||||
return (
|
||||
<Pressable
|
||||
key={f.id}
|
||||
onPress={() => {
|
||||
try { Haptics.selectionAsync(); } catch {}
|
||||
onLayersChange((prev) =>
|
||||
prev.map((l) => (l.id === selectedLayer.id ? { ...l, fontId: f.id } : l))
|
||||
);
|
||||
}}
|
||||
style={{
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 10,
|
||||
backgroundColor: isActive ? "#D4AF37" : "#242429",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: isActive ? "#0C0C0E" : "#FFFFFF",
|
||||
fontSize: 10.5,
|
||||
fontWeight: isActive ? "900" : "700",
|
||||
fontFamily: f.fontFamily,
|
||||
}}
|
||||
>
|
||||
{f.label}
|
||||
</Text>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
{/* Alt Sıra 2: Hızlı Döndürme Açısı Hapları */}
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
|
||||
<Text style={{ color: "#71717A", fontSize: 10, fontWeight: "700" }}>EĞİM:</Text>
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 6 }}>
|
||||
@@ -573,6 +647,94 @@ export const QrCanvasStudio: React.FC<QrCanvasStudioProps> = ({
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
{/* Alt Sıra 3: Kavis (Eğri Yazı) Stepper & Preset Hapları */}
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
|
||||
<Text style={{ color: "#71717A", fontSize: 10, fontWeight: "700" }}>KAVİS:</Text>
|
||||
|
||||
{/* Stepper Butonları (- / +) */}
|
||||
<View style={{ flexDirection: "row", alignItems: "center", backgroundColor: "#242429", borderRadius: 10, paddingHorizontal: 4, paddingVertical: 2, gap: 4 }}>
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
try { Haptics.selectionAsync(); } catch {}
|
||||
onLayersChange((prev) =>
|
||||
prev.map((l) =>
|
||||
l.id === selectedLayer.id
|
||||
? { ...l, curveDegree: Math.max(-100, (l.curveDegree || 0) - 5) }
|
||||
: l
|
||||
)
|
||||
);
|
||||
}}
|
||||
hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }}
|
||||
style={{ width: 22, height: 22, borderRadius: 11, backgroundColor: "rgba(255,255,255,0.12)", alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<Ionicons name="remove" size={13} color="#FFFFFF" />
|
||||
</Pressable>
|
||||
|
||||
<Text style={{ color: "#D4AF37", fontSize: 11, fontWeight: "900", minWidth: 30, textAlign: "center" }}>
|
||||
{(selectedLayer.curveDegree || 0)}°
|
||||
</Text>
|
||||
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
try { Haptics.selectionAsync(); } catch {}
|
||||
onLayersChange((prev) =>
|
||||
prev.map((l) =>
|
||||
l.id === selectedLayer.id
|
||||
? { ...l, curveDegree: Math.min(100, (l.curveDegree || 0) + 5) }
|
||||
: l
|
||||
)
|
||||
);
|
||||
}}
|
||||
hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }}
|
||||
style={{ width: 22, height: 22, borderRadius: 11, backgroundColor: "rgba(255,255,255,0.12)", alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<Ionicons name="add" size={13} color="#FFFFFF" />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{/* Hazır Kavis Presetleri */}
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 6 }}>
|
||||
{[
|
||||
{ label: "0° (Düz)", deg: 0 },
|
||||
{ label: "20° ◠", deg: 20 },
|
||||
{ label: "35° ◠", deg: 35 },
|
||||
{ label: "50° ◠", deg: 50 },
|
||||
{ label: "70° ◠", deg: 70 },
|
||||
{ label: "-35° ◡", deg: -35 },
|
||||
{ label: "-50° ◡", deg: -50 },
|
||||
].map((item) => {
|
||||
const isActive = (selectedLayer.curveDegree || 0) === item.deg;
|
||||
return (
|
||||
<Pressable
|
||||
key={item.deg}
|
||||
onPress={() => {
|
||||
try { Haptics.selectionAsync(); } catch {}
|
||||
onLayersChange((prev) =>
|
||||
prev.map((l) => (l.id === selectedLayer.id ? { ...l, curveDegree: item.deg } : l))
|
||||
);
|
||||
}}
|
||||
style={{
|
||||
paddingHorizontal: 9,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 10,
|
||||
backgroundColor: isActive ? "#D4AF37" : "#242429",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: isActive ? "#0C0C0E" : "#FFFFFF",
|
||||
fontSize: 10.5,
|
||||
fontWeight: isActive ? "800" : "600",
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</Text>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Platform } from "react-native";
|
||||
|
||||
export interface CanvasTextLayer {
|
||||
id: string;
|
||||
text: string;
|
||||
@@ -13,6 +15,7 @@ export interface CanvasTextLayer {
|
||||
hasShadow?: boolean;
|
||||
letterSpacing?: number;
|
||||
fontWeight?: "normal" | "600" | "700" | "800" | "900";
|
||||
curveDegree?: number; // -100 (Aşağı Kavis ◡) ile +100 (Yukarı Kavis ◠) arası kavis derecesi
|
||||
}
|
||||
|
||||
export interface CanvasQrConfig {
|
||||
@@ -30,40 +33,53 @@ export const CANVAS_FONT_OPTIONS = [
|
||||
{
|
||||
id: "sans",
|
||||
label: "MODERN",
|
||||
fontFamily: "System, -apple-system, Roboto, 'Helvetica Neue', Arial, sans-serif",
|
||||
fontFamily: Platform.OS === "ios" ? "System" : "sans-serif",
|
||||
},
|
||||
{
|
||||
id: "serif",
|
||||
label: "ZARİF",
|
||||
fontFamily: "Georgia, 'Times New Roman', serif",
|
||||
fontFamily: Platform.OS === "ios" ? "Georgia" : "serif",
|
||||
},
|
||||
{
|
||||
id: "times",
|
||||
label: "KLASİK",
|
||||
fontFamily: "'Times New Roman', Times, Georgia, serif",
|
||||
fontFamily: Platform.OS === "ios" ? "Times New Roman" : "serif",
|
||||
},
|
||||
{
|
||||
id: "typewriter",
|
||||
label: "DAKTİLO",
|
||||
fontFamily: "Courier, 'Courier New', monospace",
|
||||
fontFamily: Platform.OS === "ios" ? "Courier" : "monospace",
|
||||
},
|
||||
{
|
||||
id: "palatino",
|
||||
label: "LÜKS",
|
||||
fontFamily: "Palatino, 'Palatino Linotype', Georgia, serif",
|
||||
fontFamily: Platform.OS === "ios" ? "Palatino" : "serif",
|
||||
},
|
||||
{
|
||||
id: "trebuchet",
|
||||
label: "DİNAMİK",
|
||||
fontFamily: "'Trebuchet MS', 'Segoe UI', Arial, sans-serif",
|
||||
fontFamily: Platform.OS === "ios" ? "Trebuchet MS" : "sans-serif",
|
||||
},
|
||||
{
|
||||
id: "heavy",
|
||||
label: "GÜÇLÜ",
|
||||
fontFamily: "Impact, 'Arial Black', sans-serif",
|
||||
fontFamily: Platform.OS === "ios" ? "Impact" : "sans-serif-black",
|
||||
},
|
||||
];
|
||||
|
||||
export function getCanvasFontFamily(fontId?: string): string {
|
||||
if (!fontId) return Platform.OS === "ios" ? "System" : "sans-serif";
|
||||
const found = CANVAS_FONT_OPTIONS.find((f) => f.id === fontId);
|
||||
if (found) {
|
||||
return found.fontFamily;
|
||||
}
|
||||
if (fontId.includes(",")) {
|
||||
const first = fontId.split(",")[0].trim().replace(/['"]/g, "");
|
||||
return first || (Platform.OS === "ios" ? "System" : "sans-serif");
|
||||
}
|
||||
return fontId;
|
||||
}
|
||||
|
||||
export const CANVAS_COLOR_PALETTE = [
|
||||
{ id: "gold", color: "#D4AF37", label: "Altın" },
|
||||
{ id: "white", color: "#FFFFFF", label: "Beyaz" },
|
||||
@@ -106,11 +122,6 @@ export const QR_BORDER_COLORS = [
|
||||
{ id: "none", color: "transparent", label: "Yok" },
|
||||
];
|
||||
|
||||
export function getCanvasFontFamily(fontId?: string): string {
|
||||
const found = CANVAS_FONT_OPTIONS.find((f) => f.id === fontId);
|
||||
return found ? found.fontFamily : "System, -apple-system, Roboto, sans-serif";
|
||||
}
|
||||
|
||||
export function getDefaultQrConfigForTemplate(
|
||||
templateKey: string,
|
||||
canvasWidth: number,
|
||||
|
||||
@@ -24,39 +24,47 @@ export interface QrTemplateProps extends SvgProps {
|
||||
hideQr?: boolean;
|
||||
}
|
||||
|
||||
import { Platform } from "react-native";
|
||||
|
||||
// Türkçe karakterleri (İ, ı, Ş, ş, Ğ, ğ, Ü, ü, Ö, ö, Ç, ç) eksiksiz destekleyen sistem fontları
|
||||
export const FONT_OPTIONS = [
|
||||
{
|
||||
id: "sans",
|
||||
label: "Modern Sans",
|
||||
fontFamily: "System, -apple-system, Roboto, 'Helvetica Neue', Arial, sans-serif",
|
||||
fontFamily: Platform.OS === "ios" ? "System" : "sans-serif",
|
||||
},
|
||||
{
|
||||
id: "serif",
|
||||
label: "Zarif Serif",
|
||||
fontFamily: "Georgia, 'Times New Roman', serif",
|
||||
fontFamily: Platform.OS === "ios" ? "Georgia" : "serif",
|
||||
},
|
||||
{
|
||||
id: "times",
|
||||
label: "Klasik (Times)",
|
||||
fontFamily: "'Times New Roman', Times, Georgia, serif",
|
||||
fontFamily: Platform.OS === "ios" ? "Times New Roman" : "serif",
|
||||
},
|
||||
{
|
||||
id: "palatino",
|
||||
label: "Lüks Palatino",
|
||||
fontFamily: "Palatino, 'Palatino Linotype', Georgia, serif",
|
||||
fontFamily: Platform.OS === "ios" ? "Palatino" : "serif",
|
||||
},
|
||||
{
|
||||
id: "trebuchet",
|
||||
label: "Dinamik",
|
||||
fontFamily: "'Trebuchet MS', 'Segoe UI', Arial, sans-serif",
|
||||
fontFamily: Platform.OS === "ios" ? "Trebuchet MS" : "sans-serif",
|
||||
},
|
||||
];
|
||||
|
||||
export function getFontFamily(fontId?: string, defaultFamily = "Georgia, serif"): string {
|
||||
if (!fontId) return defaultFamily;
|
||||
export function getFontFamily(fontId?: string, defaultFamily = "Georgia"): string {
|
||||
if (!fontId) {
|
||||
return defaultFamily.includes(",") ? defaultFamily.split(",")[0].trim().replace(/['"]/g, "") : defaultFamily;
|
||||
}
|
||||
const found = FONT_OPTIONS.find((f) => f.id === fontId);
|
||||
return found ? found.fontFamily : defaultFamily;
|
||||
if (found) return found.fontFamily;
|
||||
if (fontId.includes(",")) {
|
||||
return fontId.split(",")[0].trim().replace(/['"]/g, "");
|
||||
}
|
||||
return fontId;
|
||||
}
|
||||
|
||||
// Çok satırlı (Enter / \n) ve dikey kaydırmalı (offsetY) SVG metin render fonksiyonu
|
||||
|
||||
Reference in New Issue
Block a user