manuo
Version:
UI component library for React Native + NativeWind, mobile-first and platform-specific.
557 lines (537 loc) • 19.8 kB
JavaScript
// src/nativeWindInterop.ts
import { Text } from "react-native";
import { cssInterop } from "nativewind";
cssInterop(Text, { className: "style" });
// src/components/Button.tsx
import { Platform } from "react-native";
// src/components/ios/ButtonIOS.tsx
import {
Text as RNText,
View as RNView,
ActivityIndicator,
Pressable
} from "react-native";
import { cssInterop as cssInterop2 } from "nativewind";
import { jsx, jsxs } from "react/jsx-runtime";
var Text2 = cssInterop2(RNText, { className: "style" });
var View = cssInterop2(RNView, { className: "style" });
var ButtonIOS = ({
title,
icon,
onPress,
className,
textClassName,
variant = "primary",
loading = false,
loaderColor,
...rest
}) => {
const baseContainer = "px-4 py-2 min-w-[44px] min-h-[44px] rounded-xl flex-row items-center justify-center gap-2 active:opacity-70 transition";
const variants = {
primary: "bg-blue-500",
secondary: "bg-white border border-blue-500",
tonal: "bg-blue-100",
plain: "bg-transparent"
};
const textStyles = {
primary: "text-white text-xl font-semibold",
// ⬅️ mai mare, bold
secondary: "text-blue-500 text-xl font-medium",
// ⬅️ mai mic, ca în poză
tonal: "text-blue-700 text-base font-semibold",
// ⬅️ ca primary, dar cu altă culoare
plain: "text-blue-500 text-base font-medium"
// ⬅️ subtil
};
const defaultLoaderColor = loaderColor ?? (variant === "primary" ? "#ffffff" : "#007AFF");
return /* @__PURE__ */ jsx(
Pressable,
{
onPress,
disabled: loading,
accessibilityRole: "button",
accessibilityLabel: title,
...rest,
children: /* @__PURE__ */ jsxs(View, { className: `${baseContainer} ${variants[variant]} ${className ?? ""}`, children: [
loading ? /* @__PURE__ */ jsx(ActivityIndicator, { size: "small", color: defaultLoaderColor }) : icon,
!!title && /* @__PURE__ */ jsx(
Text2,
{
className: `${textStyles[variant]} ${textClassName ?? ""}`,
children: title
}
)
] })
}
);
};
// src/components/android/ButtonAndroid.tsx
import {
Text as RNText2,
View as RNView2,
ActivityIndicator as ActivityIndicator2,
TouchableNativeFeedback
} from "react-native";
import { cssInterop as cssInterop3 } from "nativewind";
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
var Text3 = cssInterop3(RNText2, { className: "style" });
var View2 = cssInterop3(RNView2, { className: "style" });
var ButtonAndroid = ({
title,
icon,
onPress,
className,
textClassName,
variant = "primary",
loading = false,
loaderColor
}) => {
const baseContainer = "px-4 py-2 min-w-[48px] min-h-[48px] rounded-full flex-row items-center justify-center gap-2 overflow-hidden";
const variants = {
primary: "bg-blue-500",
secondary: "bg-white border border-gray-400",
tonal: "bg-blue-100",
plain: "bg-transparent"
};
const textStyles = {
primary: "text-white text-lg font-semibold uppercase",
secondary: "text-black text-lg font-medium uppercase",
tonal: "text-blue-800 text-base font-semibold uppercase",
plain: "text-black text-base font-medium uppercase"
};
const defaultLoaderColor = loaderColor ?? (variant === "primary" ? "#fff" : "#2196F3");
return /* @__PURE__ */ jsx2(
TouchableNativeFeedback,
{
onPress,
disabled: loading,
background: TouchableNativeFeedback.Ripple("#B0BEC5", false),
children: /* @__PURE__ */ jsxs2(View2, { className: `${baseContainer} ${variants[variant]} ${className ?? ""}`, children: [
loading ? /* @__PURE__ */ jsx2(ActivityIndicator2, { size: "small", color: defaultLoaderColor }) : icon,
!!title && /* @__PURE__ */ jsx2(Text3, { className: `${textStyles[variant]} ${textClassName ?? ""}`, children: title })
] })
}
);
};
// src/components/Button.tsx
import { jsx as jsx3 } from "react/jsx-runtime";
var Button = (props) => {
const isIOS = Platform.OS === "ios" || Platform.OS === "web";
return isIOS ? /* @__PURE__ */ jsx3(ButtonIOS, { ...props }) : /* @__PURE__ */ jsx3(ButtonAndroid, { ...props });
};
// src/components/TextDemo.tsx
import { Text as RNText3 } from "react-native";
import { cssInterop as cssInterop4 } from "nativewind";
import { jsx as jsx4 } from "react/jsx-runtime";
var Text4 = cssInterop4(RNText3, { className: "style" });
var TextDemo = ({ children, className, ...props }) => {
return /* @__PURE__ */ jsx4(Text4, { className: `text-base ${className ?? ""}`, ...props, children });
};
// src/utils/cn.ts
function cn(...classes) {
return classes.filter(Boolean).join(" ");
}
// src/components/Card.tsx
import { Platform as Platform2 } from "react-native";
// src/components/ios/CardIOS.tsx
import { View as RNView3, Text as RNText4, Image as RNImage, Pressable as Pressable2 } from "react-native";
import { cssInterop as cssInterop5 } from "nativewind";
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
var View3 = cssInterop5(RNView3, { className: "style" });
var Text5 = cssInterop5(RNText4, { className: "style" });
var Image = cssInterop5(RNImage, { className: "style" });
var CardIOS = ({
image,
badge,
title,
subtitle,
description,
buttonLabel = "GET",
icon,
onPress,
className
}) => {
return /* @__PURE__ */ jsx5(Pressable2, { onPress, className: `rounded-2xl bg-white shadow-md ${className ?? ""}`, children: /* @__PURE__ */ jsxs3(View3, { className: "overflow-hidden rounded-2xl p-4", children: [
badge && /* @__PURE__ */ jsx5(Text5, { className: "mb-2 rounded-full bg-gray-100 px-2 py-0.5 text-xs font-semibold uppercase text-gray-500 w-fit", children: badge }),
image && /* @__PURE__ */ jsx5(
Image,
{
source: { uri: image },
className: "h-48 w-full rounded-xl mb-3",
resizeMode: "cover"
}
),
/* @__PURE__ */ jsx5(Text5, { className: "text-xl font-bold text-black", children: title }),
subtitle && /* @__PURE__ */ jsx5(Text5, { className: "text-sm text-gray-500", children: subtitle }),
description && /* @__PURE__ */ jsx5(Text5, { className: "mt-1 text-sm text-gray-600", children: description }),
/* @__PURE__ */ jsxs3(View3, { className: "mt-4 flex-row items-center justify-between", children: [
/* @__PURE__ */ jsxs3(View3, { className: "flex-row items-center gap-2", children: [
icon,
!!subtitle && /* @__PURE__ */ jsxs3(View3, { children: [
/* @__PURE__ */ jsx5(Text5, { className: "text-sm font-medium text-black", children: subtitle }),
!!description && /* @__PURE__ */ jsx5(Text5, { className: "text-xs text-gray-400", children: description })
] })
] }),
/* @__PURE__ */ jsx5(View3, { className: "rounded-full bg-blue-500 px-4 py-1.5", children: /* @__PURE__ */ jsx5(Text5, { className: "text-sm font-bold text-white", children: buttonLabel }) })
] })
] }) });
};
// src/components/android/CardAndroid.tsx
import {
View as RNView4,
Text as RNText5,
Image as RNImage2,
TouchableNativeFeedback as TouchableNativeFeedback2
} from "react-native";
import { cssInterop as cssInterop6 } from "nativewind";
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
var View4 = cssInterop6(RNView4, { className: "style" });
var Text6 = cssInterop6(RNText5, { className: "style" });
var Image2 = cssInterop6(RNImage2, { className: "style" });
var CardAndroid = ({
image,
title,
subtitle,
description,
buttonLabel = "Install",
onPress,
className,
icon
}) => {
return /* @__PURE__ */ jsx6(
TouchableNativeFeedback2,
{
onPress,
background: TouchableNativeFeedback2.Ripple("#E0E0E0", false),
children: /* @__PURE__ */ jsxs4(View4, { className: `bg-white rounded-xl shadow-sm p-4 ${className ?? ""}`, children: [
image && /* @__PURE__ */ jsx6(
Image2,
{
source: { uri: image },
className: "mb-4 h-40 w-full rounded-lg",
resizeMode: "cover"
}
),
/* @__PURE__ */ jsx6(Text6, { className: "text-lg font-bold text-black", children: title }),
!!subtitle && /* @__PURE__ */ jsx6(Text6, { className: "mt-0.5 text-xs uppercase text-gray-500 tracking-wider", children: subtitle }),
!!description && /* @__PURE__ */ jsx6(Text6, { className: "mt-1 text-sm text-gray-600 leading-tight", children: description }),
/* @__PURE__ */ jsxs4(View4, { className: "mt-4 flex-row items-center justify-between", children: [
/* @__PURE__ */ jsxs4(View4, { className: "flex-row items-center gap-2", children: [
icon,
!!subtitle && /* @__PURE__ */ jsx6(Text6, { className: "text-xs text-gray-500", children: subtitle })
] }),
/* @__PURE__ */ jsx6(View4, { className: "rounded-full bg-gray-100 px-4 py-1", children: /* @__PURE__ */ jsx6(Text6, { className: "text-sm font-semibold text-blue-600", children: buttonLabel }) })
] })
] })
}
);
};
// src/components/Card.tsx
import { jsx as jsx7 } from "react/jsx-runtime";
var Card = (props) => {
const isIOS = Platform2.OS === "ios" || Platform2.OS === "web";
return isIOS ? /* @__PURE__ */ jsx7(CardIOS, { ...props }) : /* @__PURE__ */ jsx7(CardAndroid, { ...props });
};
// src/components/ListItem.tsx
import {
SectionList,
Platform as Platform3,
Text as Text9
} from "react-native";
// src/components/ios/ListItemIOS.tsx
import {
Text as RNText6,
View as RNView5,
Pressable as Pressable3,
Image as RNImage3
} from "react-native";
import { cssInterop as cssInterop7 } from "nativewind";
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
var View5 = cssInterop7(RNView5, { className: "style" });
var Text7 = cssInterop7(RNText6, { className: "style" });
var Image3 = cssInterop7(RNImage3, { className: "style" });
var ListItemIOS = ({
title,
subtitle,
rightText = "100+",
icon,
className,
onPress
}) => {
return /* @__PURE__ */ jsx8(Pressable3, { onPress, children: /* @__PURE__ */ jsxs5(View5, { className: `flex-row items-center justify-between bg-white px-4 py-3 ${className ?? ""}`, children: [
/* @__PURE__ */ jsxs5(View5, { className: "flex-row items-center gap-3", children: [
icon ?? /* @__PURE__ */ jsx8(View5, { className: "h-10 w-10 rounded-xl bg-blue-500" }),
/* @__PURE__ */ jsxs5(View5, { children: [
/* @__PURE__ */ jsx8(Text7, { className: "text-base font-semibold text-black", children: title }),
/* @__PURE__ */ jsx8(Text7, { className: "text-sm text-gray-500", children: subtitle })
] })
] }),
/* @__PURE__ */ jsx8(Text7, { className: "text-xs text-gray-500", children: rightText })
] }) });
};
// src/components/android/ListItemAndroid.tsx
import {
Text as RNText7,
View as RNView6,
TouchableNativeFeedback as TouchableNativeFeedback3
} from "react-native";
import { cssInterop as cssInterop8 } from "nativewind";
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
var View6 = cssInterop8(RNView6, { className: "style" });
var Text8 = cssInterop8(RNText7, { className: "style" });
var ListItemAndroid = ({
title,
subtitle,
rightText = "100+",
icon,
className,
onPress
}) => {
return /* @__PURE__ */ jsx9(TouchableNativeFeedback3, { onPress, background: TouchableNativeFeedback3.Ripple("#E0E0E0", false), children: /* @__PURE__ */ jsxs6(View6, { className: `flex-row items-center justify-between bg-white px-4 py-3 border-b border-gray-100 ${className ?? ""}`, children: [
/* @__PURE__ */ jsxs6(View6, { className: "flex-row items-center gap-3", children: [
icon ?? /* @__PURE__ */ jsx9(View6, { className: "h-10 w-10 rounded-md bg-blue-500" }),
/* @__PURE__ */ jsxs6(View6, { children: [
/* @__PURE__ */ jsx9(Text8, { className: "text-sm font-semibold text-black", children: title }),
/* @__PURE__ */ jsx9(Text8, { className: "text-xs text-gray-500", children: subtitle })
] })
] }),
/* @__PURE__ */ jsx9(Text8, { className: "text-xs text-gray-500", children: rightText })
] }) });
};
// src/components/ListItem.tsx
import { jsx as jsx10 } from "react/jsx-runtime";
var ListSection = ({
sections,
renderItem,
sectionHeaderClassName
}) => {
const renderDefaultItem = ({ item }) => Platform3.OS === "ios" ? /* @__PURE__ */ jsx10(ListItemIOS, { ...item }) : /* @__PURE__ */ jsx10(ListItemAndroid, { ...item });
return /* @__PURE__ */ jsx10(
SectionList,
{
sections,
keyExtractor: (item, index) => `${item.title}-${index}`,
renderItem: renderItem ?? renderDefaultItem,
renderSectionHeader: ({ section }) => /* @__PURE__ */ jsx10(
Text9,
{
className: `text-xs uppercase font-bold text-gray-400 px-4 py-2 ${sectionHeaderClassName ?? ""}`,
children: section.title
}
),
stickySectionHeadersEnabled: true,
contentContainerStyle: { paddingBottom: 24 }
}
);
};
// src/components/ProgressIndicator.tsx
import { useEffect, useRef } from "react";
import { Animated, View as RNView7, Text as RNText8 } from "react-native";
import { cssInterop as cssInterop9 } from "nativewind";
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
var View8 = cssInterop9(RNView7, { className: "style" });
var Text10 = cssInterop9(RNText8, { className: "style" });
var AnimatedView = cssInterop9(Animated.View, { className: "style" });
var ProgressBar = ({
progress,
label = "Progress Indicator",
className = "",
barClassName = "",
trackClassName = "",
thickness = 5
}) => {
const animatedWidth = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(animatedWidth, {
toValue: progress,
duration: 300,
useNativeDriver: false
}).start();
}, [progress]);
const widthInterpolation = animatedWidth.interpolate({
inputRange: [0, 1],
outputRange: ["0%", "100%"]
});
return /* @__PURE__ */ jsxs7(View8, { className: `w-full ${className}`, children: [
!!label && /* @__PURE__ */ jsx11(Text10, { className: "mb-2 text-center text-xs font-medium text-gray-600", children: label }),
/* @__PURE__ */ jsx11(
View8,
{
style: { height: thickness },
className: `w-full overflow-hidden rounded-full bg-gray-200 ${trackClassName}`,
children: /* @__PURE__ */ jsx11(
AnimatedView,
{
style: { width: widthInterpolation, height: thickness },
className: `rounded-full bg-blue-500 ${barClassName}`
}
)
}
)
] });
};
// src/components/SegmentControl.tsx
import { Platform as Platform4 } from "react-native";
// src/components/ios/SegmentedControlIOS.tsx
import { useEffect as useEffect2, useRef as useRef2, useState } from "react";
import {
Animated as Animated2,
Pressable as Pressable4,
View as RNView8,
Text as RNText9
} from "react-native";
import { cssInterop as cssInterop10 } from "nativewind";
import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
var View9 = cssInterop10(RNView8, { className: "style" });
var Text11 = cssInterop10(RNText9, { className: "style" });
var AnimatedView2 = cssInterop10(Animated2.View, { className: "style" });
var SegmentedControlIOS = ({
options,
selectedIndex = 0,
onChange
}) => {
const [containerWidth, setContainerWidth] = useState(0);
const [itemWidth, setItemWidth] = useState(0);
const translateX = useRef2(new Animated2.Value(0)).current;
const [currentIndex, setCurrentIndex] = useState(selectedIndex);
useEffect2(() => {
if (itemWidth === 0) return;
animateToIndex(currentIndex, selectedIndex);
setCurrentIndex(selectedIndex);
}, [selectedIndex, itemWidth]);
const animateToIndex = (from, to) => {
const steps = Math.abs(to - from);
const direction = to > from ? 1 : -1;
const delay = 70;
for (let i = 1; i <= steps; i++) {
setTimeout(() => {
Animated2.timing(translateX, {
toValue: (from + i * direction) * itemWidth,
duration: delay,
useNativeDriver: false
}).start();
}, delay * i);
}
};
const onContainerLayout = (e) => {
const width = e.nativeEvent.layout.width;
setContainerWidth(width);
setItemWidth(width / options.length);
};
return /* @__PURE__ */ jsxs8(
View9,
{
onLayout: onContainerLayout,
className: "flex-row bg-[#f0f0f5] rounded-[12px] p-[2px] overflow-hidden",
children: [
itemWidth > 0 && /* @__PURE__ */ jsx12(
AnimatedView2,
{
style: {
width: itemWidth,
transform: [{ translateX }]
},
className: "absolute top-[2px] bottom-[2px] left-0 rounded-[9px] bg-white shadow-sm z-0"
}
),
options.map((option, index) => {
const isActive = index === currentIndex;
return /* @__PURE__ */ jsx12(
Pressable4,
{
onPress: () => onChange?.(index),
className: `z-10 flex-1 items-center justify-center px-3 py-2 relative ${index !== 0 && index !== options.length - 1 ? "border-l border-gray-300" : ""}`,
children: /* @__PURE__ */ jsx12(
Text11,
{
className: `text-base font-semibold ${isActive ? "text-black" : "text-gray-500"}`,
children: option
}
)
},
index
);
})
]
}
);
};
// src/components/android/SegmentedControlAndroid.tsx
import { Pressable as Pressable5, View as View10, Text as Text12 } from "react-native";
import { jsx as jsx13 } from "react/jsx-runtime";
var SegmentedControlAndroid = ({
options,
selectedIndex = 0,
onChange
}) => {
return /* @__PURE__ */ jsx13(
View10,
{
style: {
flexDirection: "row",
borderWidth: 1,
borderColor: "#D1D5DB",
borderRadius: 24,
overflow: "hidden"
},
children: options.map((option, index) => {
const isFirst = index === 0;
const isLast = index === options.length - 1;
const isSelected = selectedIndex === index;
return /* @__PURE__ */ jsx13(
Pressable5,
{
onPress: () => onChange?.(index),
style: {
flex: 1,
backgroundColor: isSelected ? "#E0F2FE" : "#fff",
paddingVertical: 10,
alignItems: "center",
borderRightWidth: index < options.length - 1 ? 1 : 0,
borderRightColor: "#D1D5DB",
borderTopLeftRadius: isFirst ? 999 : 0,
borderBottomLeftRadius: isFirst ? 999 : 0,
borderTopRightRadius: isLast ? 999 : 0,
borderBottomRightRadius: isLast ? 999 : 0
},
children: /* @__PURE__ */ jsx13(
Text12,
{
style: {
fontSize: 14,
fontWeight: isSelected ? "600" : "400",
color: "#111827"
},
children: option
}
)
},
index
);
})
}
);
};
// src/components/SegmentControl.tsx
import { jsx as jsx14 } from "react/jsx-runtime";
var SegmentedControl = (props) => {
const isIOS = Platform4.OS === "ios" || Platform4.OS === "web";
return isIOS ? /* @__PURE__ */ jsx14(SegmentedControlIOS, { ...props }) : /* @__PURE__ */ jsx14(SegmentedControlAndroid, { ...props });
};
export {
Button,
ButtonAndroid,
ButtonIOS,
Card,
CardAndroid,
CardIOS,
ListItemAndroid,
ListItemIOS,
ListSection,
ProgressBar,
SegmentedControl,
SegmentedControlAndroid,
SegmentedControlIOS,
TextDemo,
cn
};