UNPKG

manuo

Version:

UI component library for React Native + NativeWind, mobile-first and platform-specific.

557 lines (537 loc) 19.8 kB
// 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 };