-
-
Notifications
You must be signed in to change notification settings - Fork 238
[POC] Porting Icon component from gluestack-ui V2 #341
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<typeof PrimitiveIcon> & | ||
React.RefAttributes<React.ElementRef<typeof Svg>> | ||
>; | ||
|
||
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<typeof iconStyle> | { size: number }) & | ||
React.ComponentPropsWithoutRef<typeof UIIcon>; | ||
|
||
export const Icon = React.forwardRef<React.ElementRef<typeof Svg>, IIConProps>( | ||
({ size = 'md', className, ...props }, ref) => { | ||
if (typeof size === 'number') { | ||
return <UIIcon ref={ref} {...props} className={iconStyle({ className })} size={size} />; | ||
} else if ((props.height !== undefined || props.width !== undefined) && size === undefined) { | ||
return <UIIcon ref={ref} {...props} className={iconStyle({ className })} />; | ||
} | ||
return <UIIcon ref={ref} {...props} className={iconStyle({ size, className })} />; | ||
} | ||
); | ||
|
||
type ParameterTypes = Omit<Parameters<typeof createIcon>[0], 'Root'>; | ||
|
||
const createIconUI = ({ ...props }: ParameterTypes) => { | ||
const UIIconCreateIcon = createIcon({ | ||
Root: Svg, | ||
...props, | ||
}) as React.ForwardRefExoticComponent< | ||
React.ComponentPropsWithoutRef<typeof PrimitiveIcon> & | ||
React.RefAttributes<React.ElementRef<typeof Svg>> | ||
>; | ||
|
||
return React.forwardRef<React.ElementRef<typeof Svg>>( | ||
( | ||
{ | ||
className, | ||
size, | ||
...inComingProps | ||
}: VariantProps<typeof iconStyle> & React.ComponentPropsWithoutRef<typeof UIIconCreateIcon>, | ||
ref | ||
) => { | ||
return ( | ||
<UIIconCreateIcon | ||
ref={ref} | ||
{...inComingProps} | ||
className={iconStyle({ size, class: className })} | ||
/> | ||
); | ||
} | ||
); | ||
}; | ||
export { createIconUI as createIcon }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<IconProps> = React.ForwardRefExoticComponent< | ||
IIconProps & IconProps & React.RefAttributes<IconProps> | ||
>; | ||
|
||
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<IconProps>({ | ||
Root, | ||
path, | ||
d, | ||
...initialProps | ||
}: { Root: React.ComponentType<IconProps> } & CreateIconOptions) { | ||
const IconTemp = forwardRef((props: any, ref?: any) => { | ||
let children = path; | ||
if (d && (!path || Object.keys(path).length === 0)) { | ||
children = <Path fill="currentColor" d={d} />; | ||
} | ||
|
||
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 ( | ||
<Root | ||
{...resolvedProps} | ||
{...colorProps} | ||
role={role} | ||
ref={ref} | ||
{...sizeProps} | ||
{...sizeStyle} | ||
> | ||
{React.Children.count(children) > 0 ? ( | ||
<G> | ||
{React.Children.map(children, (child, i) => ( | ||
<ChildPath | ||
key={child?.key ?? i} | ||
element={child} | ||
{...child?.props} | ||
/> | ||
))} | ||
</G> | ||
) : null} | ||
</Root> | ||
); | ||
}); | ||
|
||
const Icon = IconTemp as IIconComponentType< | ||
IconProps | { fill?: ColorValue; stroke?: ColorValue } | ||
>; | ||
return Icon; | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -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<IconProps> = 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, | ||||||||||||||||||||||||
}); | ||||||||||||||||||||||||
}; | ||||||||||||||||||||||||
Comment on lines
+36
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve type safety and prop validation in ChildPath component. The component has several type-safety concerns:
-const ChildPath = ({ element, fill, stroke: pathStroke }: any) => {
+interface ChildPathProps {
+ element: React.ReactElement | null;
+ fill?: string;
+ stroke?: string;
+}
+
+const ChildPath = ({ element, fill, stroke: pathStroke }: ChildPathProps) => { Also, consider adding prop-types validation if you're targeting environments where TypeScript types aren't available at runtime. |
||||||||||||||||||||||||
|
||||||||||||||||||||||||
export function createIcon<IconProps>({ | ||||||||||||||||||||||||
Root, | ||||||||||||||||||||||||
path, | ||||||||||||||||||||||||
d, | ||||||||||||||||||||||||
...initialProps | ||||||||||||||||||||||||
}: { Root: React.ComponentType<IconProps> } & CreateIconOptions) { | ||||||||||||||||||||||||
const IconTemp = forwardRef((props: any, ref?: any) => { | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve type safety in IconTemp component. The component props are typed as -const IconTemp = forwardRef((props: any, ref?: any) => {
+interface IconTempProps extends IIconProps {
+ stroke?: string;
+ color?: string;
+ role?: string;
+ type?: 'svg' | 'font';
+ sx?: { h?: number };
+ size?: number;
+}
+const IconTemp = forwardRef<unknown, IconTempProps>((props, ref) => { 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||
let children = path; | ||||||||||||||||||||||||
if (d && (!path || Object.keys(path).length === 0)) { | ||||||||||||||||||||||||
children = <path fill="currentColor" d={d} />; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
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 ( | ||||||||||||||||||||||||
<Root | ||||||||||||||||||||||||
{...resolvedProps} | ||||||||||||||||||||||||
{...colorProps} | ||||||||||||||||||||||||
role={role} | ||||||||||||||||||||||||
ref={ref} | ||||||||||||||||||||||||
{...sizeProps} | ||||||||||||||||||||||||
{...sizeStyle} | ||||||||||||||||||||||||
> | ||||||||||||||||||||||||
{React.Children.count(children) > 0 ? ( | ||||||||||||||||||||||||
<g> | ||||||||||||||||||||||||
{React.Children.map(children, (child, i) => ( | ||||||||||||||||||||||||
<ChildPath | ||||||||||||||||||||||||
key={child?.key ?? i} | ||||||||||||||||||||||||
element={child} | ||||||||||||||||||||||||
{...child?.props} | ||||||||||||||||||||||||
/> | ||||||||||||||||||||||||
))} | ||||||||||||||||||||||||
</g> | ||||||||||||||||||||||||
) : null} | ||||||||||||||||||||||||
</Root> | ||||||||||||||||||||||||
); | ||||||||||||||||||||||||
}); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
const Icon = IconTemp as IIconComponentType< | ||||||||||||||||||||||||
IconProps | { fill?: ColorValue; stroke?: ColorValue } | ||||||||||||||||||||||||
>; | ||||||||||||||||||||||||
return Icon; | ||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { createIcon } from './createIcon'; | ||
export { PrimitiveIcon, Svg, UIIcon } from './primitiveIcon'; | ||
export type { IPrimitiveIcon } from './primitiveIcon'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { createIcon } from './createIcon'; | ||
export { PrimitiveIcon, Svg, UIIcon } from './primitiveIcon'; | ||
export type { IPrimitiveIcon } from './primitiveIcon'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve type safety by avoiding
any
types.The
defaultProps
andtype
properties are typed asany
, which reduces type safety. Consider defining specific types based on their expected values.📝 Committable suggestion