mirror of
https://github.com/khairul169/home-lab.git
synced 2026-10-02 17:13:57 +07:00
chore: initial commit
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ComponentPropsWithClassName } from "@/types/components";
|
||||
import { View } from "react-native";
|
||||
|
||||
type Props = ComponentPropsWithClassName<typeof View>;
|
||||
|
||||
const Box = ({ className, ...props }: Props) => {
|
||||
return <View style={cn(className)} {...props} />;
|
||||
};
|
||||
|
||||
export default Box;
|
||||
@@ -0,0 +1,96 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { type VariantProps, cva } from "class-variance-authority";
|
||||
import { Pressable, Text } from "react-native";
|
||||
import React from "react";
|
||||
import Slot from "./Slot";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"flex flex-row items-center justify-center rounded-md",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary",
|
||||
secondary: "bg-secondary",
|
||||
destructive: "bg-red-500",
|
||||
ghost: "",
|
||||
link: "text-primary underline-offset-4",
|
||||
outline: "border border-primary",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4",
|
||||
sm: "h-8 px-2",
|
||||
lg: "h-12 px-8",
|
||||
icon: "h-10 w-10 px-0",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const buttonTextVariants = cva("text-center font-medium", {
|
||||
variants: {
|
||||
variant: {
|
||||
default: "text-primary-foreground",
|
||||
secondary: "text-secondary-foreground",
|
||||
destructive: "text-white",
|
||||
ghost: "text-primary",
|
||||
link: "text-primary-foreground underline",
|
||||
outline: "text-primary",
|
||||
},
|
||||
size: {
|
||||
default: "text-base",
|
||||
sm: "text-sm",
|
||||
lg: "text-xl",
|
||||
icon: "text-base",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
});
|
||||
|
||||
interface ButtonProps
|
||||
extends React.ComponentPropsWithoutRef<typeof Pressable>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
label?: string;
|
||||
labelClasses?: string;
|
||||
className?: string;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
function Button({
|
||||
label,
|
||||
labelClasses,
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
icon,
|
||||
...props
|
||||
}: ButtonProps) {
|
||||
const textStyles = cn(
|
||||
buttonTextVariants({ variant, size, className: labelClasses })
|
||||
);
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={({ pressed }) =>
|
||||
cn(
|
||||
buttonVariants({ variant, size, className }),
|
||||
pressed ? "opacity-60" : "opacity-100"
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{icon ? <Slot.View style={textStyles}>{icon}</Slot.View> : null}
|
||||
|
||||
{label ? <Text style={textStyles}>{label}</Text> : null}
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
export { buttonVariants, buttonTextVariants };
|
||||
|
||||
export default Button;
|
||||
@@ -0,0 +1,3 @@
|
||||
import Ionicons from "@expo/vector-icons/Ionicons";
|
||||
|
||||
export { Ionicons };
|
||||
@@ -0,0 +1,206 @@
|
||||
import * as React from "react";
|
||||
import {
|
||||
Image as RNImage,
|
||||
Pressable as RNPressable,
|
||||
Text as RNText,
|
||||
View as RNView,
|
||||
StyleSheet,
|
||||
type PressableStateCallbackType,
|
||||
type ImageProps as RNImageProps,
|
||||
type ImageStyle as RNImageStyle,
|
||||
type PressableProps as RNPressableprops,
|
||||
type TextProps as RNTextProps,
|
||||
type ViewProps as RNViewProps,
|
||||
type StyleProp,
|
||||
} from "react-native";
|
||||
|
||||
const Pressable = React.forwardRef<
|
||||
React.ElementRef<typeof RNPressable>,
|
||||
RNPressableprops
|
||||
>((props, forwardedRef) => {
|
||||
const { children, ...pressableslotProps } = props;
|
||||
|
||||
if (!React.isValidElement(children)) {
|
||||
console.log("Slot.Pressable - Invalid asChild element", children);
|
||||
return null;
|
||||
}
|
||||
|
||||
return React.cloneElement<
|
||||
React.ComponentPropsWithoutRef<typeof RNPressable>,
|
||||
React.ElementRef<typeof RNPressable>
|
||||
>(isTextChildren(children) ? <></> : children, {
|
||||
...mergeProps(pressableslotProps, children.props),
|
||||
ref: forwardedRef
|
||||
? composeRefs(forwardedRef, (children as any).ref)
|
||||
: (children as any).ref,
|
||||
});
|
||||
});
|
||||
|
||||
Pressable.displayName = "SlotPressable";
|
||||
|
||||
const View = React.forwardRef<React.ElementRef<typeof RNView>, RNViewProps>(
|
||||
(props, forwardedRef) => {
|
||||
const { children, ...viewSlotProps } = props;
|
||||
|
||||
if (!React.isValidElement(children)) {
|
||||
console.log("Slot.View - Invalid asChild element", children);
|
||||
return null;
|
||||
}
|
||||
|
||||
return React.cloneElement<
|
||||
React.ComponentPropsWithoutRef<typeof RNView>,
|
||||
React.ElementRef<typeof RNView>
|
||||
>(isTextChildren(children) ? <></> : children, {
|
||||
...mergeProps(viewSlotProps, children.props),
|
||||
ref: forwardedRef
|
||||
? composeRefs(forwardedRef, (children as any).ref)
|
||||
: (children as any).ref,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
View.displayName = "SlotView";
|
||||
|
||||
const Text = React.forwardRef<React.ElementRef<typeof RNText>, RNTextProps>(
|
||||
(props, forwardedRef) => {
|
||||
const { children, ...textSlotProps } = props;
|
||||
|
||||
if (!React.isValidElement(children)) {
|
||||
console.log("Slot.Text - Invalid asChild element", children);
|
||||
return null;
|
||||
}
|
||||
|
||||
return React.cloneElement<
|
||||
React.ComponentPropsWithoutRef<typeof RNText>,
|
||||
React.ElementRef<typeof RNText>
|
||||
>(isTextChildren(children) ? <></> : children, {
|
||||
...mergeProps(textSlotProps, children.props),
|
||||
ref: forwardedRef
|
||||
? composeRefs(forwardedRef, (children as any).ref)
|
||||
: (children as any).ref,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
Text.displayName = "SlotText";
|
||||
|
||||
type ImageSlotProps = RNImageProps & {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const Image = React.forwardRef<
|
||||
React.ElementRef<typeof RNImage>,
|
||||
ImageSlotProps
|
||||
>((props, forwardedRef) => {
|
||||
const { children, ...imageSlotProps } = props;
|
||||
|
||||
if (!React.isValidElement(children)) {
|
||||
console.log("Slot.Image - Invalid asChild element", children);
|
||||
return null;
|
||||
}
|
||||
|
||||
return React.cloneElement<
|
||||
React.ComponentPropsWithoutRef<typeof RNImage>,
|
||||
React.ElementRef<typeof RNImage>
|
||||
>(isTextChildren(children) ? <></> : children, {
|
||||
...mergeProps(imageSlotProps, children.props),
|
||||
ref: forwardedRef
|
||||
? composeRefs(forwardedRef, (children as any).ref)
|
||||
: (children as any).ref,
|
||||
});
|
||||
});
|
||||
|
||||
Image.displayName = "SlotImage";
|
||||
|
||||
const Slot = { Image, Pressable, Text, View };
|
||||
export default Slot;
|
||||
|
||||
// This project uses code from WorkOS/Radix Primitives.
|
||||
// The code is licensed under the MIT License.
|
||||
// https://github.com/radix-ui/primitives/tree/main
|
||||
|
||||
function composeRefs<T>(...refs: (React.Ref<T> | undefined)[]) {
|
||||
return (node: T) =>
|
||||
refs.forEach((ref) => {
|
||||
if (typeof ref === "function") {
|
||||
ref(node);
|
||||
} else if (ref != null) {
|
||||
(ref as React.MutableRefObject<T>).current = node;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
type AnyProps = Record<string, any>;
|
||||
|
||||
function mergeProps(slotProps: AnyProps, childProps: AnyProps) {
|
||||
// all child props should override
|
||||
const overrideProps = { ...childProps };
|
||||
|
||||
for (const propName in childProps) {
|
||||
const slotPropValue = slotProps[propName];
|
||||
const childPropValue = childProps[propName];
|
||||
|
||||
const isHandler = /^on[A-Z]/.test(propName);
|
||||
if (isHandler) {
|
||||
// if the handler exists on both, we compose them
|
||||
if (slotPropValue && childPropValue) {
|
||||
overrideProps[propName] = (...args: unknown[]) => {
|
||||
childPropValue(...args);
|
||||
slotPropValue(...args);
|
||||
};
|
||||
}
|
||||
// but if it exists only on the slot, we use only this one
|
||||
else if (slotPropValue) {
|
||||
overrideProps[propName] = slotPropValue;
|
||||
}
|
||||
}
|
||||
// if it's `style`, we merge them
|
||||
else if (propName === "style") {
|
||||
overrideProps[propName] = combineStyles(slotPropValue, childPropValue);
|
||||
} else if (propName === "className") {
|
||||
overrideProps[propName] = [slotPropValue, childPropValue]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
}
|
||||
}
|
||||
|
||||
return { ...slotProps, ...overrideProps };
|
||||
}
|
||||
|
||||
type PressableStyle = RNPressableprops["style"];
|
||||
type ImageStyle = StyleProp<RNImageStyle>;
|
||||
type Style = PressableStyle | ImageStyle;
|
||||
|
||||
function combineStyles(slotStyle?: Style, childValue?: Style) {
|
||||
if (typeof slotStyle === "function" && typeof childValue === "function") {
|
||||
return (state: PressableStateCallbackType) => {
|
||||
return StyleSheet.flatten([slotStyle(state), childValue(state)]);
|
||||
};
|
||||
}
|
||||
if (typeof slotStyle === "function") {
|
||||
return (state: PressableStateCallbackType) => {
|
||||
return childValue
|
||||
? StyleSheet.flatten([slotStyle(state), childValue])
|
||||
: slotStyle(state);
|
||||
};
|
||||
}
|
||||
if (typeof childValue === "function") {
|
||||
return (state: PressableStateCallbackType) => {
|
||||
return slotStyle
|
||||
? StyleSheet.flatten([slotStyle, childValue(state)])
|
||||
: childValue(state);
|
||||
};
|
||||
}
|
||||
|
||||
return StyleSheet.flatten([slotStyle, childValue].filter(Boolean));
|
||||
}
|
||||
|
||||
export function isTextChildren(
|
||||
children:
|
||||
| React.ReactNode
|
||||
| ((state: PressableStateCallbackType) => React.ReactNode)
|
||||
) {
|
||||
return Array.isArray(children)
|
||||
? children.every((child) => typeof child === "string")
|
||||
: typeof children === "string";
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from "react";
|
||||
import Box from "./Box";
|
||||
import { ComponentPropsWithClassName } from "@/types/components";
|
||||
|
||||
type StackProps = ComponentPropsWithClassName<typeof Box> & {
|
||||
direction?: "row" | "column";
|
||||
};
|
||||
|
||||
const Stack = ({ direction = "row", className, ...props }: StackProps) => {
|
||||
return (
|
||||
<Box
|
||||
className={[
|
||||
"flex",
|
||||
direction === "row"
|
||||
? "flex-row items-center"
|
||||
: "flex-col items-stretch",
|
||||
className,
|
||||
]}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const HStack = (props: StackProps) => <Stack direction="row" {...props} />;
|
||||
const VStack = (props: StackProps) => <Stack direction="column" {...props} />;
|
||||
|
||||
export { HStack, VStack };
|
||||
export default Stack;
|
||||
@@ -0,0 +1,5 @@
|
||||
import Box from "./Box";
|
||||
import Button from "./Button";
|
||||
import Stack, { HStack, VStack } from "./Stack";
|
||||
|
||||
export { Box, Button, Stack, HStack, VStack };
|
||||
Reference in New Issue
Block a user