diff --git a/apps/showcase/app/button.tsx b/apps/showcase/app/button.tsx
index 5cf26bbc..bf8efc28 100644
--- a/apps/showcase/app/button.tsx
+++ b/apps/showcase/app/button.tsx
@@ -1,6 +1,8 @@
+import { Apple, Bot } from 'lucide-react-native';
import { View } from 'react-native';
import { Button } from '~/components/ui/button';
import { Text } from '~/components/ui/text';
+import { Icon } from '~/lib/icon';
export default function ButtonScreen() {
return (
@@ -29,6 +31,14 @@ export default function ButtonScreen() {
+
+
);
}
diff --git a/apps/showcase/lib/icon.tsx b/apps/showcase/lib/icon.tsx
new file mode 100644
index 00000000..b8c53584
--- /dev/null
+++ b/apps/showcase/lib/icon.tsx
@@ -0,0 +1,87 @@
+import React from 'react';
+import { cva, type VariantProps } from 'class-variance-authority';
+import { cssInterop } from 'nativewind';
+import { createIcon, PrimitiveIcon, IPrimitiveIcon, Svg } from '~/lib/rnr-icon';
+
+export const UIIcon = createIcon({
+ Root: PrimitiveIcon,
+}) as React.ForwardRefExoticComponent<
+ React.ComponentPropsWithoutRef &
+ React.RefAttributes>
+>;
+
+const iconStyle = cva('text-typography-950 fill-none pointer-events-none', {
+ variants: {
+ size: {
+ '2xs': 'h-3 w-3',
+ xs: 'h-3.5 w-3.5',
+ sm: 'h-4 w-4',
+ md: 'h-[18px] w-[18px]',
+ lg: 'h-5 w-5',
+ xl: 'h-6 w-6',
+ },
+ },
+ defaultVariants: {
+ size: 'md',
+ },
+});
+
+cssInterop(UIIcon, {
+ className: {
+ target: 'style',
+ nativeStyleToProp: {
+ height: true,
+ width: true,
+ fill: true,
+ color: 'classNameColor',
+ stroke: true,
+ },
+ },
+});
+
+type IIConProps = IPrimitiveIcon &
+ (VariantProps | { size: number }) &
+ React.ComponentPropsWithoutRef;
+
+export const Icon = React.forwardRef, IIConProps>(
+ ({ size = 'md', className, ...props }, ref) => {
+ if (typeof size === 'number') {
+ return ;
+ } else if ((props.height !== undefined || props.width !== undefined) && size === undefined) {
+ return ;
+ }
+ return ;
+ }
+);
+
+type ParameterTypes = Omit[0], 'Root'>;
+
+const createIconUI = ({ ...props }: ParameterTypes) => {
+ const UIIconCreateIcon = createIcon({
+ Root: Svg,
+ ...props,
+ }) as React.ForwardRefExoticComponent<
+ React.ComponentPropsWithoutRef &
+ React.RefAttributes>
+ >;
+
+ return React.forwardRef>(
+ (
+ {
+ className,
+ size,
+ ...inComingProps
+ }: VariantProps & React.ComponentPropsWithoutRef,
+ ref
+ ) => {
+ return (
+
+ );
+ }
+ );
+};
+export { createIconUI as createIcon };
diff --git a/apps/showcase/lib/rnr-icon/createIcon/index.tsx b/apps/showcase/lib/rnr-icon/createIcon/index.tsx
new file mode 100644
index 00000000..7c797df4
--- /dev/null
+++ b/apps/showcase/lib/rnr-icon/createIcon/index.tsx
@@ -0,0 +1,125 @@
+import React, { forwardRef } from 'react';
+import type { ColorValue, ViewProps } from 'react-native';
+import { Path, G } from 'react-native-svg';
+
+interface CreateIconOptions {
+ /**
+ * The icon `svg` viewBox
+ * @default "0 0 24 24"
+ */
+ viewBox?: string;
+ /**
+ * The `svg` path or group element
+ * @type React.ReactElement | React.ReactElement[]
+ */
+ path?: React.ReactElement | React.ReactElement[];
+ /**
+ * If the `svg` has a single path, simply copy the path's `d` attribute
+ */
+ d?: string;
+ /**
+ * The display name useful in the dev tools
+ */
+ displayName?: string;
+ /**
+ * Default props automatically passed to the component; overwritable
+ */
+ defaultProps?: any;
+ type?: any;
+}
+
+export interface IIconProps extends ViewProps {}
+
+export type IIconComponentType = React.ForwardRefExoticComponent<
+ IIconProps & IconProps & React.RefAttributes
+>;
+
+const ChildPath = ({ element, fill, stroke: pathStroke }: any) => {
+ const pathStrokeColor = pathStroke || '';
+ const fillColor = fill || '';
+
+ if (!element) {
+ return null;
+ }
+
+ return React.cloneElement(element, {
+ fill: fillColor ? fillColor : 'currentColor',
+ stroke: pathStrokeColor,
+ });
+};
+
+export function createIcon({
+ Root,
+ path,
+ d,
+ ...initialProps
+}: { Root: React.ComponentType } & CreateIconOptions) {
+ const IconTemp = forwardRef((props: any, ref?: any) => {
+ let children = path;
+ if (d && (!path || Object.keys(path).length === 0)) {
+ children = ;
+ }
+
+ const finalProps = {
+ ...initialProps,
+ ...props,
+ };
+
+ const {
+ stroke = 'currentColor',
+ color,
+ role = 'img',
+ ...resolvedProps
+ } = finalProps;
+ let type = resolvedProps.type;
+ if (type === undefined) {
+ type = 'svg';
+ }
+ let colorProps = {};
+ if (color) {
+ colorProps = { ...colorProps, color: color };
+ }
+ if (stroke) {
+ colorProps = { ...colorProps, stroke: stroke };
+ }
+
+ let sizeProps = {};
+ let sizeStyle = {};
+ if (type === 'font') {
+ if (resolvedProps.sx) {
+ sizeProps = { ...sizeProps, fontSize: resolvedProps?.sx?.h };
+ }
+ if (resolvedProps.size) {
+ // sizeProps = { ...sizeProps, fontSize: resolvedProps?.size };
+ }
+ }
+
+ return (
+
+ {React.Children.count(children) > 0 ? (
+
+ {React.Children.map(children, (child, i) => (
+
+ ))}
+
+ ) : null}
+
+ );
+ });
+
+ const Icon = IconTemp as IIconComponentType<
+ IconProps | { fill?: ColorValue; stroke?: ColorValue }
+ >;
+ return Icon;
+}
diff --git a/apps/showcase/lib/rnr-icon/createIcon/index.web.tsx b/apps/showcase/lib/rnr-icon/createIcon/index.web.tsx
new file mode 100644
index 00000000..7b1e0779
--- /dev/null
+++ b/apps/showcase/lib/rnr-icon/createIcon/index.web.tsx
@@ -0,0 +1,128 @@
+import React, { forwardRef } from 'react';
+import type { ColorValue, ViewProps } from 'react-native';
+
+interface CreateIconOptions {
+ /**
+ * The icon `svg` viewBox
+ * @default "0 0 24 24"
+ */
+ viewBox?: string;
+ /**
+ * The `svg` path or group element
+ * @type React.ReactElement | React.ReactElement[]
+ */
+ path?: React.ReactElement | React.ReactElement[];
+ /**
+ * If the `svg` has a single path, simply copy the path's `d` attribute
+ */
+ d?: string;
+ /**
+ * The display name useful in the dev tools
+ */
+ displayName?: string;
+ /**
+ * Default props automatically passed to the component; overwritable
+ */
+ defaultProps?: any;
+ type?: any;
+}
+
+export interface IIconProps extends ViewProps {}
+
+export type IIconComponentType = React.ForwardRefExoticComponent<
+ IIconProps & IconProps
+>;
+
+const ChildPath = ({ element, fill, stroke: pathStroke }: any) => {
+ const pathStrokeColor = pathStroke || '';
+ const fillColor = fill || '';
+
+ if (!element) {
+ return null;
+ }
+
+ if (element.type === React.Fragment) {
+ return element;
+ }
+
+ return React.cloneElement(element, {
+ fill: fillColor ? fillColor : 'currentColor',
+ stroke: pathStrokeColor,
+ });
+};
+
+export function createIcon({
+ Root,
+ path,
+ d,
+ ...initialProps
+}: { Root: React.ComponentType } & CreateIconOptions) {
+ const IconTemp = forwardRef((props: any, ref?: any) => {
+ let children = path;
+ if (d && (!path || Object.keys(path).length === 0)) {
+ children = ;
+ }
+
+ const finalProps = {
+ ...initialProps,
+ ...props,
+ };
+
+ const {
+ stroke = 'currentColor',
+ color,
+ role = 'img',
+ ...resolvedProps
+ } = finalProps;
+ let type = resolvedProps.type;
+ if (type === undefined) {
+ type = 'svg';
+ }
+ let colorProps = {};
+ if (color) {
+ colorProps = { ...colorProps, color: color };
+ }
+ if (stroke) {
+ colorProps = { ...colorProps, stroke: stroke };
+ }
+
+ let sizeProps = {};
+ let sizeStyle = {};
+ if (type === 'font') {
+ if (resolvedProps.sx) {
+ sizeProps = { ...sizeProps, fontSize: resolvedProps?.sx?.h };
+ }
+ if (resolvedProps.size) {
+ // sizeProps = { ...sizeProps, fontSize: resolvedProps?.size };
+ }
+ }
+
+ return (
+
+ {React.Children.count(children) > 0 ? (
+
+ {React.Children.map(children, (child, i) => (
+
+ ))}
+
+ ) : null}
+
+ );
+ });
+
+ const Icon = IconTemp as IIconComponentType<
+ IconProps | { fill?: ColorValue; stroke?: ColorValue }
+ >;
+ return Icon;
+}
diff --git a/apps/showcase/lib/rnr-icon/index.tsx b/apps/showcase/lib/rnr-icon/index.tsx
new file mode 100644
index 00000000..65932e98
--- /dev/null
+++ b/apps/showcase/lib/rnr-icon/index.tsx
@@ -0,0 +1,3 @@
+export { createIcon } from './createIcon';
+export { PrimitiveIcon, Svg, UIIcon } from './primitiveIcon';
+export type { IPrimitiveIcon } from './primitiveIcon';
diff --git a/apps/showcase/lib/rnr-icon/index.web.tsx b/apps/showcase/lib/rnr-icon/index.web.tsx
new file mode 100644
index 00000000..65932e98
--- /dev/null
+++ b/apps/showcase/lib/rnr-icon/index.web.tsx
@@ -0,0 +1,3 @@
+export { createIcon } from './createIcon';
+export { PrimitiveIcon, Svg, UIIcon } from './primitiveIcon';
+export type { IPrimitiveIcon } from './primitiveIcon';
diff --git a/apps/showcase/lib/rnr-icon/primitiveIcon/index.tsx b/apps/showcase/lib/rnr-icon/primitiveIcon/index.tsx
new file mode 100644
index 00000000..aea77b2c
--- /dev/null
+++ b/apps/showcase/lib/rnr-icon/primitiveIcon/index.tsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import { Svg } from 'react-native-svg';
+import { createIcon } from '../createIcon';
+
+export { Svg };
+export type IPrimitiveIcon = {
+ height?: number | string;
+ width?: number | string;
+ fill?: string;
+ color?: string;
+ size?: number | string;
+ stroke?: string;
+ as?: React.ElementType;
+ className?: string;
+ classNameColor?: string;
+ style?: any;
+};
+
+export const PrimitiveIcon = React.forwardRef, IPrimitiveIcon>(
+ (
+ {
+ height,
+ width,
+ fill,
+ color,
+ classNameColor,
+ size,
+ stroke = 'currentColor',
+ as: AsComp,
+ style,
+ ...props
+ },
+ ref
+ ) => {
+ color = color ?? classNameColor;
+ const sizeProps = React.useMemo(() => {
+ if (size) return { size };
+ if (height && width) return { height, width };
+ if (height) return { height };
+ if (width) return { width };
+ return {};
+ }, [size, height, width]);
+
+ let colorProps = {};
+ if (fill) {
+ colorProps = { ...colorProps, fill: fill };
+ }
+ if (stroke !== 'currentColor') {
+ colorProps = { ...colorProps, stroke: stroke };
+ } else if (stroke === 'currentColor' && color !== undefined) {
+ colorProps = { ...colorProps, stroke: color };
+ }
+
+ if (AsComp) {
+ return ;
+ }
+ return ;
+ }
+);
+
+export const UIIcon = createIcon({
+ Root: PrimitiveIcon,
+});
diff --git a/apps/showcase/lib/rnr-icon/primitiveIcon/index.web.tsx b/apps/showcase/lib/rnr-icon/primitiveIcon/index.web.tsx
new file mode 100644
index 00000000..5f1acd28
--- /dev/null
+++ b/apps/showcase/lib/rnr-icon/primitiveIcon/index.web.tsx
@@ -0,0 +1,64 @@
+import React from 'react';
+import { createIcon } from '../createIcon';
+
+const accessClassName = (style: any) => {
+ const styleObject = Array.isArray(style) ? style[0] : style;
+ const keys = Object.keys(styleObject);
+ return styleObject[keys[1]];
+};
+
+const Svg = React.forwardRef, React.ComponentPropsWithoutRef<'svg'>>(
+ ({ style, className, ...props }, ref) => {
+ const calculateClassName = React.useMemo(() => {
+ return className === undefined ? accessClassName(style) : className;
+ }, [className, style]);
+ return ;
+ }
+);
+
+export type IPrimitiveIcon = {
+ height?: number | string;
+ width?: number | string;
+ fill?: string;
+ color?: string;
+ size?: number | string;
+ stroke?: string;
+ as?: React.ElementType;
+ className?: string;
+ classNameColor?: string;
+ style?: any;
+};
+
+const PrimitiveIcon = React.forwardRef, IPrimitiveIcon>(
+ ({ height, width, fill, color, classNameColor, size, stroke, as: AsComp, ...props }, ref) => {
+ color = color ?? classNameColor;
+ const sizeProps = React.useMemo(() => {
+ if (size) return { size };
+ if (height && width) return { height, width };
+ if (height) return { height };
+ if (width) return { width };
+ return {};
+ }, [size, height, width]);
+
+ let colorProps = {};
+ if (fill) {
+ colorProps = { ...colorProps, fill: fill };
+ }
+ if (stroke !== 'currentColor') {
+ colorProps = { ...colorProps, stroke: stroke };
+ } else if (stroke === 'currentColor' && color !== undefined) {
+ colorProps = { ...colorProps, stroke: color };
+ }
+
+ if (AsComp) {
+ return ;
+ }
+ return ;
+ }
+);
+
+const UIIcon = createIcon({
+ Root: PrimitiveIcon,
+});
+
+export { PrimitiveIcon, Svg, UIIcon };