gluestack-components

安装量: 35
排名: #19552

安装

npx skills add https://github.com/thebushidocollective/han --skill gluestack-components

gluestack-ui - Components Expert knowledge of gluestack-ui's universal component library for building accessible, performant UI across React and React Native platforms. Overview gluestack-ui provides 50+ unstyled, accessible components that work seamlessly on web and mobile. Components are copy-pasteable into your project and styled with NativeWind (Tailwind CSS for React Native). Key Concepts Component Installation Add components using the CLI:

Initialize gluestack-ui in your project

npx gluestack-ui init

Add individual components

npx gluestack-ui add button npx gluestack-ui add input npx gluestack-ui add modal

Add multiple components

npx gluestack-ui add button input select modal

Add all components

npx gluestack-ui add --all Component Anatomy Every gluestack-ui component follows a consistent pattern: import { Button , ButtonText , ButtonSpinner , ButtonIcon } from '@/components/ui/button' ; // Components are composable with sub-components < Button size = " md " variant = " solid " action = " primary "

< ButtonIcon as = { PlusIcon } /> < ButtonText

Add Item </ ButtonText

< ButtonSpinner /> </ Button

Variants, Sizes, and Actions Components support consistent prop APIs: // Button variants < Button variant = " solid "

Solid </ Button

< Button variant = " outline "

Outline </ Button

< Button variant = " link "

Link </ Button

// Button sizes < Button size = " xs "

Extra Small </ Button

< Button size = " sm "

Small </ Button

< Button size = " md "

Medium </ Button

< Button size = " lg "

Large </ Button

< Button size = " xl "

Extra Large </ Button

// Button actions (semantic colors) < Button action = " primary "

Primary </ Button

< Button action = " secondary "

Secondary </ Button

< Button action = " positive "

Positive </ Button

< Button action = " negative "

Negative </ Button

Core Components Button Interactive button with loading states and icons: import { Button , ButtonText , ButtonSpinner , ButtonIcon } from '@/components/ui/button' ; import { SaveIcon } from 'lucide-react-native' ; function SaveButton ( { isLoading , onPress } : { isLoading : boolean ; onPress : ( ) => void } ) { return ( < Button size = " md " variant = " solid " action = " primary " isDisabled = { isLoading } onPress = { onPress }

{ isLoading ? ( < ButtonSpinner /> ) : ( < ButtonIcon as = { SaveIcon } /> ) } < ButtonText

{ isLoading ? 'Saving...' : 'Save' } </ ButtonText

</ Button

) ; } Input Text input with labels and validation: import { FormControl , FormControlLabel , FormControlLabelText , FormControlHelper , FormControlHelperText , FormControlError , FormControlErrorIcon , FormControlErrorText , } from '@/components/ui/form-control' ; import { Input , InputField , InputSlot , InputIcon } from '@/components/ui/input' ; import { AlertCircleIcon , MailIcon } from 'lucide-react-native' ; function EmailInput ( { value , onChange , error } : { value : string ; onChange : ( text : string ) => void ; error ? : string ; } ) { return ( < FormControl isInvalid = { ! ! error }

< FormControlLabel

< FormControlLabelText

Email </ FormControlLabelText

</ FormControlLabel

< Input variant = " outline " size = " md "

< InputSlot pl = " $3 "

< InputIcon as = { MailIcon } /> </ InputSlot

< InputField placeholder = " Enter your email " value = { value } onChangeText = { onChange } keyboardType = " email-address " autoCapitalize = " none " /> </ Input

{ error ? ( < FormControlError

< FormControlErrorIcon as = { AlertCircleIcon } /> < FormControlErrorText

{ error } </ FormControlErrorText

</ FormControlError

) : ( < FormControlHelper

< FormControlHelperText

We'll never share your email </ FormControlHelperText

</ FormControlHelper

) } </ FormControl

) ; } Select Dropdown selection component: import { Select , SelectTrigger , SelectInput , SelectIcon , SelectPortal , SelectBackdrop , SelectContent , SelectDragIndicatorWrapper , SelectDragIndicator , SelectItem , } from '@/components/ui/select' ; import { ChevronDownIcon } from 'lucide-react-native' ; function CountrySelect ( { value , onValueChange } : { value : string ; onValueChange : ( value : string ) => void ; } ) { return ( < Select selectedValue = { value } onValueChange = { onValueChange }

< SelectTrigger variant = " outline " size = " md "

< SelectInput placeholder = " Select country " /> < SelectIcon as = { ChevronDownIcon } mr = " $3 " /> </ SelectTrigger

< SelectPortal

< SelectBackdrop /> < SelectContent

< SelectDragIndicatorWrapper

< SelectDragIndicator /> </ SelectDragIndicatorWrapper

< SelectItem label = " United States " value = " us " /> < SelectItem label = " Canada " value = " ca " /> < SelectItem label = " United Kingdom " value = " uk " /> < SelectItem label = " Australia " value = " au " /> </ SelectContent

</ SelectPortal

</ Select

) ; } Modal Dialog overlay component: import { Modal , ModalBackdrop , ModalContent , ModalHeader , ModalCloseButton , ModalBody , ModalFooter , } from '@/components/ui/modal' ; import { Heading } from '@/components/ui/heading' ; import { Text } from '@/components/ui/text' ; import { Button , ButtonText } from '@/components/ui/button' ; import { CloseIcon , Icon } from '@/components/ui/icon' ; function ConfirmModal ( { isOpen , onClose , onConfirm , title , message } : { isOpen : boolean ; onClose : ( ) => void ; onConfirm : ( ) => void ; title : string ; message : string ; } ) { return ( < Modal isOpen = { isOpen } onClose = { onClose } size = " md "

< ModalBackdrop /> < ModalContent

< ModalHeader

< Heading size = " lg "

{ title } </ Heading

< ModalCloseButton

< Icon as = { CloseIcon } /> </ ModalCloseButton

</ ModalHeader

< ModalBody

< Text

{ message } </ Text

</ ModalBody

< ModalFooter

< Button variant = " outline " action = " secondary " onPress = { onClose }

< ButtonText

Cancel </ ButtonText

</ Button

< Button action = " negative " onPress = { onConfirm }

< ButtonText

Delete </ ButtonText

</ Button

</ ModalFooter

</ ModalContent

</ Modal

) ; } Toast Notification component: import { Toast , ToastTitle , ToastDescription , useToast , } from '@/components/ui/toast' ; function NotificationExample ( ) { const toast = useToast ( ) ; const showToast = ( ) => { toast . show ( { placement : 'top' , render : ( { id } ) => ( < Toast nativeID = { toast- ${ id } } action = " success " variant = " solid "

< ToastTitle

Success! </ ToastTitle

< ToastDescription

Your changes have been saved. </ ToastDescription

</ Toast

) , } ) ; } ; return ( < Button onPress = { showToast }

< ButtonText

Show Toast </ ButtonText

</ Button

) ; } Accordion Expandable content sections: import { Accordion , AccordionItem , AccordionHeader , AccordionTrigger , AccordionTitleText , AccordionIcon , AccordionContent , AccordionContentText , } from '@/components/ui/accordion' ; import { ChevronDownIcon , ChevronUpIcon } from 'lucide-react-native' ; function FAQAccordion ( { items } : { items : { question : string ; answer : string } [ ] } ) { return ( < Accordion type = " multiple " defaultValue = { [ 'item-0' ] }

{ items . map ( ( item , index ) => ( < AccordionItem key = { index } value = { item- ${ index } }

< AccordionHeader

< AccordionTrigger

{ ( { isExpanded } ) => ( <

< AccordionTitleText

{ item . question } </ AccordionTitleText

< AccordionIcon as = { isExpanded ? ChevronUpIcon : ChevronDownIcon } /> </

) } </ AccordionTrigger

</ AccordionHeader

< AccordionContent

< AccordionContentText

{ item . answer } </ AccordionContentText

</ AccordionContent

</ AccordionItem

) ) } </ Accordion

) ; } Checkbox and Radio Selection controls: import { Checkbox , CheckboxIndicator , CheckboxIcon , CheckboxLabel , } from '@/components/ui/checkbox' ; import { RadioGroup , Radio , RadioIndicator , RadioIcon , RadioLabel , } from '@/components/ui/radio' ; import { CheckIcon , CircleIcon } from 'lucide-react-native' ; function TermsCheckbox ( { isChecked , onChange } : { isChecked : boolean ; onChange : ( checked : boolean ) => void ; } ) { return ( < Checkbox value = " terms " isChecked = { isChecked } onChange = { onChange }

< CheckboxIndicator

< CheckboxIcon as = { CheckIcon } /> </ CheckboxIndicator

< CheckboxLabel

I agree to the terms and conditions </ CheckboxLabel

</ Checkbox

) ; } function ShippingOptions ( { value , onChange } : { value : string ; onChange : ( value : string ) => void ; } ) { return ( < RadioGroup value = { value } onChange = { onChange }

< Radio value = " standard "

< RadioIndicator

< RadioIcon as = { CircleIcon } /> </ RadioIndicator

< RadioLabel

Standard Shipping (5-7 days) </ RadioLabel

</ Radio

< Radio value = " express "

< RadioIndicator

< RadioIcon as = { CircleIcon } /> </ RadioIndicator

< RadioLabel

Express Shipping (2-3 days) </ RadioLabel

</ Radio

< Radio value = " overnight "

< RadioIndicator

< RadioIcon as = { CircleIcon } /> </ RadioIndicator

< RadioLabel

Overnight Shipping (1 day) </ RadioLabel

</ Radio

</ RadioGroup

) ; } Best Practices 1. Use Composition Over Configuration Compose components with sub-components for flexibility: // Good: Composable structure < Button

< ButtonIcon as = { PlusIcon } /> < ButtonText

Add </ ButtonText

</ Button

// Avoid: Prop-heavy configuration < Button icon = { PlusIcon } text = " Add " iconPosition = " left " /> 2. Leverage FormControl for Form Fields Wrap inputs in FormControl for consistent validation: < FormControl isRequired isInvalid = { ! ! error }

< FormControlLabel

< FormControlLabelText

Password </ FormControlLabelText

</ FormControlLabel

< Input

< InputField type = " password " /> </ Input

< FormControlError

< FormControlErrorText

{ error } </ FormControlErrorText

</ FormControlError

</ FormControl

  1. Handle Platform Differences Use platform-specific logic when needed: import { Platform } from 'react-native' ; function ResponsiveModal ( { children } : { children : React . ReactNode } ) { return ( < Modal size = { Platform . OS === 'web' ? 'lg' : 'full' }

< ModalContent

{ children } </ ModalContent

</ Modal

) ; } 4. Use Proper Loading States Show loading feedback for async operations: function SubmitButton ( { isLoading , onPress } : { isLoading : boolean ; onPress : ( ) => void ; } ) { return ( < Button isDisabled = { isLoading } onPress = { onPress }

{ isLoading && < ButtonSpinner mr = " $2 " /> } < ButtonText

{ isLoading ? 'Submitting...' : 'Submit' } </ ButtonText

</ Button

) ; } 5. Create Reusable Component Wrappers Build app-specific components on top of gluestack-ui: // components/app/PrimaryButton.tsx import { Button , ButtonText , ButtonSpinner } from '@/components/ui/button' ; interface PrimaryButtonProps { children : string ; isLoading ? : boolean ; onPress : ( ) => void ; } export function PrimaryButton ( { children , isLoading , onPress } : PrimaryButtonProps ) { return ( < Button size = " lg " variant = " solid " action = " primary " isDisabled = { isLoading } onPress = { onPress } className = " rounded-full "

{ isLoading && < ButtonSpinner mr = " $2 " /> } < ButtonText

{ children } </ ButtonText

</ Button

) ; } Common Patterns Form with Validation import { useState } from 'react' ; import { VStack } from '@/components/ui/vstack' ; import { Button , ButtonText } from '@/components/ui/button' ; import { FormControl , FormControlError , FormControlErrorText } from '@/components/ui/form-control' ; import { Input , InputField } from '@/components/ui/input' ; interface FormData { email : string ; password : string ; } interface FormErrors { email ? : string ; password ? : string ; } function LoginForm ( { onSubmit } : { onSubmit : ( data : FormData ) => void } ) { const [ formData , setFormData ] = useState < FormData

( { email : '' , password : '' } ) ; const [ errors , setErrors ] = useState < FormErrors

( { } ) ; const validate = ( ) : boolean => { const newErrors : FormErrors = { } ; if ( ! formData . email ) { newErrors . email = 'Email is required' ; } else if ( ! / \S + @ \S + . \S + / . test ( formData . email ) ) { newErrors . email = 'Invalid email format' ; } if ( ! formData . password ) { newErrors . password = 'Password is required' ; } else if ( formData . password . length < 8 ) { newErrors . password = 'Password must be at least 8 characters' ; } setErrors ( newErrors ) ; return Object . keys ( newErrors ) . length === 0 ; } ; const handleSubmit = ( ) => { if ( validate ( ) ) { onSubmit ( formData ) ; } } ; return ( < VStack space = " md "

< FormControl isInvalid = { ! ! errors . email }

< Input

< InputField placeholder = " Email " value = { formData . email } onChangeText = { ( text ) => setFormData ( { ... formData , email : text } ) } /> </ Input

< FormControlError

< FormControlErrorText

{ errors . email } </ FormControlErrorText

</ FormControlError

</ FormControl

< FormControl isInvalid = { ! ! errors . password }

< Input

< InputField placeholder = " Password " type = " password " value = { formData . password } onChangeText = { ( text ) => setFormData ( { ... formData , password : text } ) } /> </ Input

< FormControlError

< FormControlErrorText

{ errors . password } </ FormControlErrorText

</ FormControlError

</ FormControl

< Button onPress = { handleSubmit }

< ButtonText

Login </ ButtonText

</ Button

</ VStack

) ; } Data List with Actions import { HStack } from '@/components/ui/hstack' ; import { VStack } from '@/components/ui/vstack' ; import { Box } from '@/components/ui/box' ; import { Text } from '@/components/ui/text' ; import { Heading } from '@/components/ui/heading' ; import { Button , ButtonIcon } from '@/components/ui/button' ; import { Pressable } from '@/components/ui/pressable' ; import { TrashIcon , EditIcon } from 'lucide-react-native' ; interface Item { id : string ; title : string ; description : string ; } function ItemList ( { items , onEdit , onDelete } : { items : Item [ ] ; onEdit : ( id : string ) => void ; onDelete : ( id : string ) => void ; } ) { return ( < VStack space = " sm "

{ items . map ( ( item ) => ( < Box key = { item . id } className = " p-4 bg-background-50 rounded-lg border border-outline-200 "

< HStack justifyContent = " space-between " alignItems = " center "

< VStack space = " xs " flex = { 1 }

< Heading size = " sm "

{ item . title } </ Heading

< Text size = " sm " className = " text-typography-500 "

{ item . description } </ Text

</ VStack

< HStack space = " xs "

< Button size = " sm " variant = " outline " action = " secondary " onPress = { ( ) => onEdit ( item . id ) }

< ButtonIcon as = { EditIcon } /> </ Button

< Button size = " sm " variant = " outline " action = " negative " onPress = { ( ) => onDelete ( item . id ) }

< ButtonIcon as = { TrashIcon } /> </ Button

</ HStack

</ HStack

</ Box

) ) } </ VStack

) ; } Anti-Patterns Do Not Mix Styling Approaches // Bad: Mixing inline styles with NativeWind < Button style = { { backgroundColor : 'blue' } } className = " p-4 "

< ButtonText

Click </ ButtonText

</ Button

// Good: Use NativeWind classes consistently < Button className = " bg-blue-500 p-4 "

< ButtonText

Click </ ButtonText

</ Button

Do Not Skip Accessibility Props // Bad: Missing accessibility information < Pressable onPress = { handlePress }

< Icon as = { MenuIcon } /> </ Pressable

// Good: Include accessibility props < Pressable onPress = { handlePress } accessibilityLabel = " Open menu " accessibilityRole = " button "

< Icon as = { MenuIcon } /> </ Pressable

Do Not Ignore Platform Constraints // Bad: Using web-only APIs on native < Modal

< ModalContent onClick = { handleClick }

{ / onClick doesn't work on native / } ... </ ModalContent

</ Modal

// Good: Use cross-platform events < Modal

< ModalContent

< Pressable onPress = { handlePress }

... </ Pressable

</ ModalContent

</ Modal

Do Not Hardcode Colors // Bad: Hardcoded colors < Box className = " bg-[#3B82F6] "

< Text className = " text-[#FFFFFF] "

Hello </ Text

</ Box

// Good: Use theme tokens < Box className = " bg-primary-500 "

< Text className = " text-typography-0 "

Hello </ Text

</ Box

返回排行榜