Skip to content

Input Container

The InputContainer component provides a foundational container for building custom input interfaces. It handles common input behaviors like focus states, error states, labels, hints, and animations. This component is used internally by other input components but can also be used directly to create custom input experiences.

import { InputContainer, type InputRef } from '@space-uy/pulsar-ui';
<InputContainer label="Custom Input" onPress={() => console.log('Pressed')}>
<Text>Custom content here</Text>
</InputContainer>
PropertyTypeRequiredDefault valueDescription
onPress() => void-Function called when container is pressed
labelstring-Label text displayed above the container
hintstring-Hint text displayed below the container
errorbooleanfalseWhether the container is in error state
focusedbooleanfalseWhether the container appears focused
disabledbooleanfalseWhether the container is disabled
size'small' | 'default''default'Size variant of the container
heightnumber-Custom height override
iconNameIconName-Icon displayed on the left side
styleStyleProp<ViewStyle>-Custom styles for the outer container
contentContainerStyleStyleProp<ViewStyle>-Custom styles for the inner content container
childrenReact.ReactNode-Content to display inside the container

When using a ref, the following methods are available:

MethodTypeDescription
focus() => voidFocus the container (if applicable)
const [value, setValue] = useState('');
const [focused, setFocused] = useState(false);
<InputContainer
label="Custom Text Input"
focused={focused}
onPress={() => setFocused(true)}
hint="Enter your custom text"
>
<TextInput
value={value}
onChangeText={setValue}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
style={{ flex: 1, paddingVertical: 8 }}
placeholder="Type here..."
/>
</InputContainer>;
const [selectedValue, setSelectedValue] = useState<string | null>(null);
const [showPicker, setShowPicker] = useState(false);
<InputContainer
label="Select Option"
onPress={() => setShowPicker(true)}
iconName="ChevronDown"
>
<Text style={{ flex: 1, paddingVertical: 8 }}>
{selectedValue || 'Choose an option'}
</Text>
</InputContainer>;
const [input, setInput] = useState('');
const [hasError, setHasError] = useState(false);
const validateInput = (text: string) => {
const isValid = text.length >= 3;
setHasError(!isValid && text.length > 0);
return isValid;
};
<InputContainer
label="Validated Input"
error={hasError}
hint={
hasError ? 'Minimum 3 characters required' : 'Enter at least 3 characters'
}
onPress={() => {
/* handle press */
}}
>
<TextInput
value={input}
onChangeText={(text) => {
setInput(text);
validateInput(text);
}}
style={{ flex: 1, paddingVertical: 8 }}
placeholder="Validated input..."
/>
</InputContainer>;
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
const [showDatePicker, setShowDatePicker] = useState(false);
<InputContainer
label="Birth Date"
onPress={() => setShowDatePicker(true)}
iconName="Calendar"
hint="Select your birth date"
>
<Text
style={{
flex: 1,
paddingVertical: 8,
color: selectedDate ? colors.foreground : colors.foreground + '80',
}}
>
{selectedDate ? format(selectedDate, 'MMM dd, yyyy') : 'Select date'}
</Text>
<Icon name="ChevronDown" size={16} color={colors.foreground} />
</InputContainer>;
const [selectedItems, setSelectedItems] = useState<string[]>([]);
const [showSelector, setShowSelector] = useState(false);
const displayText =
selectedItems.length > 0
? `${selectedItems.length} items selected`
: 'Select items';
<InputContainer
label="Categories"
onPress={() => setShowSelector(true)}
iconName="Tag"
focused={showSelector}
>
<View
style={{
flex: 1,
flexDirection: 'row',
alignItems: 'center',
gap: 8,
paddingVertical: 4,
}}
>
{selectedItems.slice(0, 2).map((item) => (
<Chip key={item} text={item} size="small" />
))}
{selectedItems.length > 2 && (
<Text variant="ps" style={{ opacity: 0.7 }}>
+{selectedItems.length - 2} more
</Text>
)}
{selectedItems.length === 0 && (
<Text style={{ color: colors.foreground + '80', paddingVertical: 4 }}>
Select categories
</Text>
)}
</View>
<Icon name="ChevronDown" size={16} color={colors.foreground} />
</InputContainer>;
const [selectedFile, setSelectedFile] = useState<string | null>(null);
const [uploading, setUploading] = useState(false);
const handleFileSelect = async () => {
try {
setUploading(true);
// File selection logic here
const file = await selectFile();
setSelectedFile(file.name);
} catch (error) {
// Handle error
} finally {
setUploading(false);
}
};
<InputContainer
label="Upload Document"
onPress={handleFileSelect}
iconName="Upload"
disabled={uploading}
hint={selectedFile ? `Selected: ${selectedFile}` : 'Choose a file to upload'}
>
<View
style={{
flex: 1,
flexDirection: 'row',
alignItems: 'center',
gap: 8,
paddingVertical: 8,
}}
>
{uploading ? (
<>
<LoadingIndicator size={16} />
<Text>Uploading...</Text>
</>
) : selectedFile ? (
<>
<Icon name="File" size={16} color={colors.primary} />
<Text style={{ flex: 1 }} numberOfLines={1}>
{selectedFile}
</Text>
<IconButton
iconName="X"
size="small"
variant="transparent"
onPress={() => setSelectedFile(null)}
/>
</>
) : (
<Text style={{ color: colors.foreground + '80' }}>No file selected</Text>
)}
</View>
</InputContainer>;
const [selectedColor, setSelectedColor] = useState<string>('#3b82f6');
const [showColorPicker, setShowColorPicker] = useState(false);
<InputContainer
label="Brand Color"
onPress={() => setShowColorPicker(true)}
iconName="Palette"
hint="Choose your brand color"
>
<View
style={{
flex: 1,
flexDirection: 'row',
alignItems: 'center',
gap: 12,
paddingVertical: 8,
}}
>
<View
style={{
width: 24,
height: 24,
borderRadius: 12,
backgroundColor: selectedColor,
borderWidth: 1,
borderColor: colors.border,
}}
/>
<Text style={{ color: colors.foreground }}>{selectedColor}</Text>
</View>
<Icon name="ChevronDown" size={16} color={colors.foreground} />
</InputContainer>;
const [searchQuery, setSearchQuery] = useState('');
const [focused, setFocused] = useState(false);
const [suggestions, setSuggestions] = useState<string[]>([]);
const handleSearch = (query: string) => {
setSearchQuery(query);
// Mock suggestions
if (query) {
setSuggestions([
`${query} option 1`,
`${query} option 2`,
`${query} option 3`,
]);
} else {
setSuggestions([]);
}
};
<View>
<InputContainer
label="Search"
focused={focused}
onPress={() => {}}
iconName="Search"
hint="Start typing to see suggestions"
>
<TextInput
value={searchQuery}
onChangeText={handleSearch}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
style={{ flex: 1, paddingVertical: 8 }}
placeholder="Search for items..."
/>
</InputContainer>
{suggestions.length > 0 && (
<Card style={{ marginTop: 8 }}>
{suggestions.map((suggestion, index) => (
<Button
key={index}
text={suggestion}
variant="transparent"
onPress={() => {
setSearchQuery(suggestion);
setSuggestions([]);
}}
style={{ justifyContent: 'flex-start' }}
/>
))}
</Card>
)}
</View>;
  • The component provides a consistent foundation for all input components
  • Focus states are automatically managed with theme-appropriate styling
  • Error states display with distinctive red border and error text coloring
  • Icons are positioned consistently on the left side with proper spacing
  • Animations are smooth and respect reduced motion preferences
  • The component supports custom heights while maintaining proper touch targets
  • Theme integration ensures consistent styling across your application
  • Focus transitions: 200ms duration with smooth border color changes
  • Error state: 200ms duration with color transitions to error state
  • Press feedback: Subtle opacity changes for better user feedback
  • Icon animations: Smooth color transitions based on component state
  • The container properly handles focus states for keyboard navigation
  • Screen readers can navigate and understand the input structure
  • Error states are properly announced to assistive technology
  • All interactive elements maintain minimum touch target sizes
  • Color changes maintain proper contrast ratios for accessibility