agentsclimarketplace

Component

Skill Shankulkarni/RN-Lib-Claude/plugins/rn-lib-claude/skills/component

Ship production-grade RN libraries faster — scaffold, audit, and publish with Claude Code.

Install
npx -y skills add Shankulkarni/RN-Lib-Claude --skill component

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use when building React Native UI components for a library — Fabric-compatible, New Architecture patterns, ref forwarding, Platform.OS, accessibility, compound components, and safe rendering patterns.

SKILL.md

6.8 KB, as published. Nobody here has run it

UI Component Patterns

New Architecture (Fabric) only. RN 0.76+.

CRITICAL — Rendering Safety

Never && with potentially falsy values

// ❌ Renders "0" as text node → crashes or visual bug
{count && <Badge count={count} />}
{items.length && <List items={items} />}

// ✅ Ternary with null, or explicit boolean
{count > 0 ? <Badge count={count} /> : null}
{!!items.length && <List items={items} />}
{Boolean(count) && <Badge count={count} />}

All strings must be inside <Text>

// ❌ Crashes — string outside Text in New Architecture
<View>Hello world</View>
<View>{user.name}</View>

// ✅
<View><Text>Hello world</Text></View>
<View><Text>{user.name}</Text></View>

Use Pressable — never TouchableOpacity/TouchableHighlight

// ❌ Legacy, deprecated
<TouchableOpacity onPress={onPress}>
<TouchableHighlight onPress={onPress}>

// ✅ Pressable — composable, New Architecture native
<Pressable
  onPress={onPress}
  style={({ pressed }) => [styles.button, pressed && styles.pressed]}
>

For animated press states with Reanimated — see animations skill (GestureDetector + Gesture.Tap()).


Component Template

import { forwardRef } from 'react'
import { Pressable, StyleSheet, type PressableProps } from 'react-native'

type MyButtonProps = Omit<PressableProps, 'style'> & {
  variant?: 'primary' | 'secondary' | 'ghost'
  size?: 'sm' | 'md' | 'lg'
  style?: PressableProps['style']
}

const MyButton = forwardRef<View, MyButtonProps>(
  ({ variant = 'primary', size = 'md', style, children, ...props }, ref) => {
    return (
      <Pressable
        ref={ref}
        style={({ pressed }) => [
          styles.base,
          styles[variant],
          styles[size],
          pressed && styles.pressed,
          typeof style === 'function' ? style({ pressed }) : style,
        ]}
        accessibilityRole="button"
        {...props}
      >
        {children}
      </Pressable>
    )
  },
)

MyButton.displayName = 'MyButton'

const styles = StyleSheet.create({
  base: { alignItems: 'center', justifyContent: 'center', borderRadius: 8, borderCurve: 'continuous' },
  primary: { backgroundColor: '#000' },
  secondary: { backgroundColor: '#f0f0f0' },
  ghost: { backgroundColor: 'transparent' },
  sm: { paddingVertical: 6, paddingHorizontal: 12, minHeight: 32 },
  md: { paddingVertical: 10, paddingHorizontal: 16, minHeight: 44 },
  lg: { paddingVertical: 14, paddingHorizontal: 20, minHeight: 52 },
  pressed: { opacity: 0.7 },
})

export { MyButton }
export type { MyButtonProps }

Prop Design

// ✅ Accept color as prop — never hardcode
type ChipProps = {
  color?: string          // default to a semantic value, never '#ff0000'
  backgroundColor?: string
}

// ✅ Accept size as prop — never hardcode dimensions
type AvatarProps = {
  size?: number           // default 48
}

// ✅ Extend native props — consumers can pass testID, onLayout, etc.
type CardProps = ViewProps & {
  variant?: 'elevated' | 'outlined' | 'filled'
}

// ✅ Spread rest props always
const Card = forwardRef<View, CardProps>(({ variant = 'elevated', style, ...props }, ref) => (
  <View ref={ref} style={[styles[variant], style]} {...props} />
))

Compound Components

Prefer compound components over boolean prop explosion.

// ❌ Boolean prop explosion
<Button icon={<Icon />} iconPosition="left" hasSpinner isLoading label="Save" />

// ✅ Compound — explicit, composable
<Button onPress={save}>
  <ButtonIcon><SaveIcon /></ButtonIcon>
  <ButtonText>Save</ButtonText>
  <ButtonSpinner visible={isLoading} />
</Button>

Implementation:

type ButtonContextValue = { disabled: boolean }
const ButtonContext = createContext<ButtonContextValue>({ disabled: false })

const Button = ({ disabled = false, children, ...props }: ButtonProps) => (
  <ButtonContext.Provider value={{ disabled }}>
    <Pressable disabled={disabled} {...props}>{children}</Pressable>
  </ButtonContext.Provider>
)

const ButtonText = ({ style, ...props }: TextProps) => {
  const { disabled } = use(ButtonContext)
  return <Text style={[styles.text, disabled && styles.textDisabled, style]} {...props} />
}

const ButtonIcon = ({ children }: { children: ReactNode }) => (
  <View style={styles.icon}>{children}</View>
)

Button.Text = ButtonText
Button.Icon = ButtonIcon

Styling

const styles = StyleSheet.create({
  // ✅ borderCurve: 'continuous' — smooth iOS squircle corners
  card: { borderRadius: 12, borderCurve: 'continuous' },

  // ✅ gap for sibling spacing, padding for internal
  row: { flexDirection: 'row', gap: 8 },
  card2: { padding: 16 },

  // ✅ Platform shadows
  shadow: Platform.select({
    ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 8 },
    android: { elevation: 3 },
    default: {},
  }),
})

Images

// ❌ React Native Image — no blurhash, no priority, no caching control
import { Image } from 'react-native'

// ✅ expo-image — progressive loading, blurhash, CDN caching, priority
import { Image } from 'expo-image'

<Image
  source={{ uri: imageUri }}
  placeholder={{ blurhash }}
  contentFit="cover"
  priority="high"
  style={styles.image}
/>

Add to peer deps if library uses images:

"expo-image": ">=2.0.0"

Accessibility Checklist

  • accessibilityRole on every interactive element (button, image, header, etc.)
  • accessibilityLabel on icon-only buttons and images
  • accessibilityState={{ disabled, selected, checked }} when state changes
  • accessibilityHint for non-obvious interactions
  • Minimum touch target 44×44pt (minHeight: 44, minWidth: 44)
  • importantForAccessibility="no" on decorative elements

collapsable={false} for Native Component Children

Fabric may flatten layout-only views. If a component is a child of a native view that expects a specific child count, add:

// Prevents Fabric from removing this view as a layout optimization
<View collapsable={false} style={styles.wrapper}>
  {children}
</View>

Use sparingly — only when native code explicitly references this view.


Export Rules

// src/index.ts — export component + all types
export { Button, ButtonText, ButtonIcon } from './components/Button'
export type { ButtonProps } from './components/Button'
  • displayName required on every forwardRef and memo component
  • Spread ...props so consumers can pass testID, onLayout, style
  • No hardcoded colors, dimensions, or font sizes — always accept as props

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.