Expokit
AboutDocsComponentsChartsBlocksPricing
Sign inGet started
All components

Inputs

CalendarNewDate PickerNewFloating InputOTP InputPassword InputSearch BarSelectSliderStepperTag InputTextareaTime PickerNew

Selection

CheckboxChip GroupColor SwatchProRadio GroupRatingProSegmented ControlSwitch

Buttons

ButtonFloating Action ButtonProLike ButtonProMorph ButtonProShimmer ButtonProSlide to ConfirmProStateful Button

Feedback

AlertProgressSkeletonSpinnerToast

Overlays

Action SheetNewBottom SheetDialogDrawerNew

Display

AccordionAvatarBadgeCardCarouselProList ItemNewPagination DotsNewSwipeable RowProTimelineNew

Navigation

Bottom NavNewProgress StepsNewTabs

Motion

Number TickerProText Animation
Search Bar
Sponsor ExpoKit

Free stays free. Tip once — keep shipping.

Support
About
Docs
IntroductionWhat ExpoKit is and how it worksInstallationAdd ExpoKit to an Expo appThemingColours, radius and dark modeCLIinit, add, login and updateAI assistants (MCP)Cursor, Claude, Antigravity and moreRun on your phoneTry components in Expo GoChangelogWhat shipped recently
npx @expokit/cli@latest initQuick start
Components
Liquid Glass8 iOS 26 glass pieces3DNew14 tilt, flip and stack
Inputs 12
  • CalendarNew
  • Date PickerNew
  • Floating Input
  • OTP Input
  • Password Input
  • +7 more
Selection 7
  • Checkbox
  • Chip Group
  • Color SwatchPro
  • Radio Group
  • RatingPro
  • +2 more
Buttons 7
  • Button
  • Floating Action ButtonPro
  • Like ButtonPro
  • Morph ButtonPro
  • Shimmer ButtonPro
  • +2 more
Feedback 5
  • Alert
  • Progress
  • Skeleton
  • Spinner
  • Toast
Overlays 4
  • Action SheetNew
  • Bottom Sheet
  • Dialog
  • DrawerNew
Display 9
  • Accordion
  • Avatar
  • Badge
  • Card
  • CarouselPro
  • +4 more
Navigation 3
  • Bottom NavNew
  • Progress StepsNew
  • Tabs
Motion 2
  • Number TickerPro
  • Text Animation
70 components · 347 live variantsAll components
Charts

Cartesian

  • Area Chart
  • Bar Chart
  • Line Chart
  • Combo Chart

Circular

  • Pie & Donut
  • Radial Chart
  • Radar Chart
  • Polar AreaPro

3D & Pixel

  • Isometric BarsPro
  • Pixel BarsPro

Specialty

  • TreemapPro
  • WaterfallPro
  • BulletPro
  • TimelinePro
  • Gauge
  • Candlestick
  • Heatmap
  • Funnel

Classic

  • Sparkline
  • Progress Rings
  • MultiLine (classic)
  • StackedBar (classic)
  • Ranking Bars (classic)

Guides

  • Usage & config
  • Patterns & effects
  • Show / hide anything
  • Chart theming
  • Interactions
23 charts, built on the open-source expo-charts engineAll charts
Blocks

Screens

ChatProAn AI assistant chat screenCheckoutProA premium checkoutDashboardProA finance / analytics home screenInboxProA messaging inboxOnboardingProA minimal swipeable welcome flowProfileProAccount profile for any appSearch ResultsProA clean, monochrome search screenSettingsProAn iOS-style grouped settings screenSign InProOne configurable sign-in flow modelled on top mobile apps
9 complete screens — drop one in, wire your dataSee them on devices All blocks
Pricing
DocsComponentsInputsSearch Bar

Search Bar

Scan with Expo Go

Search Bar with all its variants, native in Expo Go. No account needed.

No Expo Go? Open in your browser

Focus pops the magnifier and springs in Cancel; suggestions drop down below.

<SearchBar />5 variantsiOS · Android · Expo Go
9:41

Sunday 18 October

9:41

Installation#

One command copies the source into your app — you own the code and can change anything.

Usage#

Colours, radius and light / dark come from useKit(). Wrap your app in <KitProvider accent="#6366f1" radius={14}> to re-skin every component at once.

Props#

PropTypeDefault
variant

Outlined pill, iOS grey field, or a round icon that expands into a field.

"default" | "filled" | "minimal""default"
size

34 / 44 / 56pt tall; font, icons, padding and radius scale too.

"sm" | "md" | "lg""md"
value / defaultValue / onChangeText

Controlled or uncontrolled query.

string—
loading

Crossfades the magnifier into a spinning arc.

booleanfalse
showCancel

Slide a Cancel button in while searching.

booleantrue (false for minimal)
cancelLabel

Cancel button text.

string"Cancel"
onCancel

After Cancel clears the query and blurs.

() => void—
onClear

After the clear button empties the query.

() => void—
align

Minimal only: where the collapsed icon rests.

"left" | "right""left"
suggestions

Shown in a dropdown panel while focused; the matching text is bolded.

{ key, label, hint?, icon?: 'recent' | 'trending' | 'search' }[]—
onSelectSuggestion

Called after a suggestion is picked (the query is set to its label).

(item) => void—
filterSuggestions

Filter suggestions by the query. Turn off to filter them yourself.

booleantrue
maxSuggestions

Most rows shown in the panel.

number6
trailing

Rendered inside the field on the right, e.g. a filter or mic button.

ReactNode—
accent

Focus ring, caret and Cancel colour.

stringtheme accent
style

Style for the root view around the field, cancel button and suggestions.

StyleProp<ViewStyle>—
…TextInputProps

placeholder, onSubmitEditing, autoFocus, …

Behaviour & accessibility#

Motion
On focus a halo glows and the magnifier pops; Cancel slides in as the field springs narrower and the clear button scales in. loading swaps the magnifier for a spinning arc.
Touch
The 'minimal' variant rests as a round icon and expands into a full field when tapped. Picking a suggestion fills the query and dismisses the keyboard.
Screen readers
The input has the search role, labelled by its placeholder. 'Clear search', Cancel and each suggestion are labelled buttons.
Web
The browser outline is replaced by the border and halo, and the suggestion panel stays open a beat after blur so clicks land.

Source#

PreviousPassword InputNextSelect

Did you like the content?

Coding agents

Chat apps

Other

9:41

With Suggestions

Search Bar

Manual installation
  1. Install dependencies

    terminal
    npx expo install react-native-svg
  2. Copy the files

    Create each file at the same path and paste its code. @/ points at your src/ folder (the default in new Expo apps). Skip any file you already have, like lib/kit.tsx.

    • src/components/ui/search-bar.tsx531 lines
    • src/lib/kit.tsx220 lines

    Want to read it first? Every file is in Source below.

  3. Use it

    App.tsx
    import { SearchBar } from "@/components/ui/search-bar";

    See Usage for a full example.

Example.tsx
import { useState } from 'react';import { SearchBar } from '@/components/ui/search-bar'; const [query, setQuery] = useState('');<SearchBar value={query} onChangeText={setQuery} placeholder="Search products" />

Every variant above has a complete, runnable example in its Code tab.

src/components/ui/search-bar.tsx
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState, type ComponentRef } from 'react';import {  Animated,  Easing,  Platform,  Pressable,  StyleSheet,  Text,  TextInput,  View,  type StyleProp,  type TextInputProps,  type ViewStyle,} from 'react-native';import Svg, { Circle, Path } from 'react-native-svg'; import { alpha, motion, useKit } from '@/lib/kit'; export type SearchBarVariant = 'default' | 'filled' | 'minimal';export type SearchBarSize = 'sm' | 'md' | 'lg'; export interface SearchSuggestion {  key: string;  label: string;  /** Muted text on the right, e.g. a category or result count. */  hint?: string;  /** Leading icon (default 'search'). */  icon?: 'recent' | 'trending' | 'search';} export interface SearchBarProps extends Omit<TextInputProps, 'style'> {  /** 'default' outlined pill · 'filled' iOS-style grey field · 'minimal' icon that expands into a field. */  variant?: SearchBarVariant;  /** 'sm' 36pt · 'md' 44pt · 'lg' 52pt. */  size?: SearchBarSize;  /** Swaps the magnifier for a spinning arc (e.g. while results load). */  loading?: boolean;  /** Slide a Cancel button in from the right while searching (default: true, except 'minimal'). */  showCancel?: boolean;  cancelLabel?: string;  /** Called after Cancel clears the query and dismisses the keyboard. */  onCancel?: () => void;  /** Called when the clear button empties the query. */  onClear?: () => void;  /** Focus colour (default: theme accent). */  accent?: string;  /** 'minimal' only: which side the collapsed icon rests on. */  align?: 'left' | 'right';  /** Shown in a dropdown panel under the bar while it is focused. */  suggestions?: SearchSuggestion[];  /** Called when a suggestion is picked (the query is set to its label first). */  onSelectSuggestion?: (item: SearchSuggestion) => void;  /** Filter suggestions by the query (default true). Turn off if you filter them yourself. */  filterSuggestions?: boolean;  /** Most suggestions to show (default 6). */  maxSuggestions?: number;  /** Rendered inside the field on the right, e.g. a mic or filter button. */  trailing?: React.ReactNode;  style?: StyleProp<ViewStyle>;} /** Every dimension comes from one table so the sizes read clearly different. */const SIZES: Record<SearchBarSize, { h: number; font: number; icon: number; slot: number; r: number; clear: number; end: number }> = {  sm: { h: 34, font: 14, icon: 14, slot: 30, r: 9, clear: 16, end: 6 },  md: { h: 44, font: 16, icon: 18, slot: 40, r: 12, clear: 20, end: 8 },  lg: { h: 56, font: 19, icon: 22, slot: 52, r: 16, clear: 24, end: 12 },}; /** react-native-web draws the browser focus outline; our border + halo is the focus indicator. */const webNoOutline = Platform.OS === 'web' ? ({ outlineStyle: 'none', outlineWidth: 0 } as object) : null; /** * Search field with a focus halo: the magnifier pops on focus, Cancel slides in as the field * springs narrower, a clear button scales in, a spinning arc shows loading and a suggestion * panel drops down while focused. The 'minimal' variant rests as a round icon and expands * into a full field when tapped. */export const SearchBar = forwardRef<ComponentRef<typeof TextInput>, SearchBarProps>(function SearchBar(  {    variant = 'default',    size = 'md',    loading = false,    showCancel,    cancelLabel = 'Cancel',    onCancel,    onClear,    accent,    align = 'left',    suggestions,    onSelectSuggestion,    filterSuggestions = true,    maxSuggestions = 6,    trailing,    style,    value,    defaultValue,    onChangeText,    onFocus,    onBlur,    placeholder = 'Search',    editable = true,    autoFocus,    ...props  },  ref) {  const { colors, radius } = useKit();  const s = SIZES[size];  const tint = accent ?? colors.accent;  const minimal = variant === 'minimal';  const cancelEnabled = !minimal && (showCancel ?? true);   const inputRef = useRef<ComponentRef<typeof TextInput>>(null);  useImperativeHandle(ref, () => inputRef.current as ComponentRef<typeof TextInput>);   const [focused, setFocused] = useState(false);  const [inner, setInner] = useState(defaultValue ?? '');  const text = value ?? inner;  const [expanded, setExpanded] = useState(!!text || !!autoFocus);  const [rowW, setRowW] = useState(0);  const [cancelW, setCancelW] = useState(0);   const open = !minimal || expanded;  const cancelShown = cancelEnabled && (focused || text.length > 0);   // JS driver: layout (widths) and colours.  const focusJS = useRef(new Animated.Value(0)).current;  const cancelJS = useRef(new Animated.Value(0)).current;  const openJS = useRef(new Animated.Value(open ? 1 : 0)).current;  // Native driver: opacity and transforms, on different views.  const halo = useRef(new Animated.Value(0)).current;  const cancelN = useRef(new Animated.Value(0)).current;  const openN = useRef(new Animated.Value(open ? 1 : 0)).current;  const icon = useRef(new Animated.Value(1)).current;  const clear = useRef(new Animated.Value(text ? 1 : 0)).current;  const busy = useRef(new Animated.Value(loading ? 1 : 0)).current;  const spin = useRef(new Animated.Value(0)).current;   useEffect(() => {    const to = focused ? 1 : 0;    Animated.timing(focusJS, { toValue: to, duration: motion.fast, easing: Easing.out(Easing.quad), useNativeDriver: false }).start();    Animated.spring(halo, { toValue: to, useNativeDriver: true, ...motion.snappy }).start();    if (focused) {      icon.setValue(0.6);      Animated.spring(icon, { toValue: 1, useNativeDriver: true, ...motion.bouncy }).start();    }  }, [focused, focusJS, halo, icon]);   useEffect(() => {    const to = cancelShown ? 1 : 0;    Animated.spring(cancelJS, { toValue: to, useNativeDriver: false, ...motion.snappy }).start();    Animated.spring(cancelN, { toValue: to, useNativeDriver: true, ...motion.snappy }).start();  }, [cancelShown, cancelJS, cancelN]);   useEffect(() => {    const to = open ? 1 : 0;    Animated.spring(openJS, { toValue: to, useNativeDriver: false, ...motion.gentle }).start();    Animated.timing(openN, { toValue: to, duration: open ? motion.normal : motion.fast, delay: open ? 80 : 0, useNativeDriver: true }).start();  }, [open, openJS, openN]);   useEffect(() => {    Animated.spring(clear, { toValue: text && editable && open ? 1 : 0, useNativeDriver: true, ...motion.bouncy }).start();  }, [text, editable, open, clear]);   useEffect(() => {    Animated.timing(busy, { toValue: loading ? 1 : 0, duration: motion.fast, useNativeDriver: true }).start();    if (!loading) return;    spin.setValue(0);    const loop = Animated.loop(Animated.timing(spin, { toValue: 1, duration: 750, easing: Easing.linear, useNativeDriver: true }));    loop.start();    return () => loop.stop();  }, [loading, busy, spin]);   // The panel closes a beat after blur so a tap on a suggestion still lands (web blurs on mousedown).  const [panelOpen, setPanelOpen] = useState(false);  const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);  useEffect(() => {    if (closeTimer.current) clearTimeout(closeTimer.current);    if (focused) setPanelOpen(true);    else closeTimer.current = setTimeout(() => setPanelOpen(false), 160);    return () => {      if (closeTimer.current) clearTimeout(closeTimer.current);    };  }, [focused]);   const query = text.trim().toLowerCase();  const visible = (suggestions ?? [])    .filter((item) => !filterSuggestions || !query || item.label.toLowerCase().includes(query))    .slice(0, maxSuggestions);   const setText = (next: string) => {    if (value === undefined) setInner(next);    onChangeText?.(next);  };   const expand = () => {    setExpanded(true);    requestAnimationFrame(() => inputRef.current?.focus());  };   const cancel = () => {    setText('');    inputRef.current?.blur();    if (minimal) setExpanded(false);    onCancel?.();  };   const select = (item: SearchSuggestion) => {    setText(item.label);    setPanelOpen(false);    inputRef.current?.blur();    onSelectSuggestion?.(item);  };   const look: Record<SearchBarVariant, { bg: string; rest: string; r: number }> = {    default: { bg: colors.card, rest: colors.input, r: radius.capsule(s.h) },    filled: { bg: colors.muted, rest: alpha(colors.muted, 0), r: Math.round(s.r * (radius.md / 12)) },    minimal: { bg: colors.muted, rest: alpha(colors.muted, 0), r: radius.capsule(s.h) },  };  const { bg, rest, r } = look[variant];  const borderColor = focusJS.interpolate({ inputRange: [0, 1], outputRange: [rest, tint] });  const fieldW = minimal ? openJS.interpolate({ inputRange: [0, 1], outputRange: [s.h, Math.max(s.h, rowW)] }) : undefined;  const slot = minimal ? s.h : s.slot;  const iconColor = focused ? tint : minimal && !open ? colors.foreground : colors.mutedForeground;   return (    <View style={[styles.root, !editable && styles.disabled, style]}>      <View        onLayout={(e) => setRowW(e.nativeEvent.layout.width)}        style={[styles.row, minimal && { justifyContent: align === 'right' ? 'flex-end' : 'flex-start' }]}      >        <Animated.View style={minimal ? { width: fieldW, height: s.h } : [styles.flex, { height: s.h }]}>          <Animated.View            pointerEvents="none"            style={[              styles.halo,              {                borderRadius: r + 4,                backgroundColor: alpha(tint, 0.18),                opacity: halo,                transform: [{ scale: halo.interpolate({ inputRange: [0, 1], outputRange: [0.96, 1] }) }],              },            ]}          />          <Animated.View style={[styles.field, { borderRadius: r, backgroundColor: bg, borderColor }]}>            <View style={[styles.slot, { width: slot }]}>              <Animated.View                style={{                  opacity: busy.interpolate({ inputRange: [0, 1], outputRange: [1, 0] }),                  transform: [                    { scale: icon },                    { rotate: icon.interpolate({ inputRange: [0, 1], outputRange: ['-30deg', '0deg'] }) },                  ],                }}              >                <Icon name="search" size={s.icon} color={iconColor} />              </Animated.View>              <Animated.View                pointerEvents="none"                style={[                  styles.center,                  { opacity: busy, transform: [{ rotate: spin.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '360deg'] }) }] },                ]}              >                <Svg width={s.icon} height={s.icon} viewBox="0 0 24 24" fill="none">                  <Circle cx={12} cy={12} r={8.5} stroke={alpha(tint, 0.2)} strokeWidth={2.2} />                  <Path d="M12 3.5a8.5 8.5 0 0 1 8.5 8.5" stroke={tint} strokeWidth={2.2} strokeLinecap="round" />                </Svg>              </Animated.View>            </View>            <Animated.View style={[styles.flex, { opacity: openN }]} pointerEvents={open ? 'auto' : 'none'}>              <TextInput                ref={inputRef}                returnKeyType="search"                autoCorrect={false}                accessibilityRole="search"                accessibilityLabel={props.accessibilityLabel ?? placeholder}                {...props}                autoFocus={autoFocus}                editable={editable}                value={text}                onChangeText={setText}                placeholder={placeholder}                placeholderTextColor={colors.mutedForeground}                selectionColor={tint}                cursorColor={tint}                onFocus={(e) => {                  setFocused(true);                  onFocus?.(e);                }}                onBlur={(e) => {                  setFocused(false);                  if (minimal && !text) setExpanded(false);                  onBlur?.(e);                }}                style={[styles.input, { color: colors.foreground, fontSize: s.font, height: s.h - 3 }, webNoOutline]}              />            </Animated.View>            <Animated.View              pointerEvents={text && open ? 'auto' : 'none'}              style={[styles.clear, { width: s.clear + 8, opacity: clear, transform: [{ scale: clear }] }]}            >              <Pressable                hitSlop={12}                accessibilityRole="button"                accessibilityLabel="Clear search"                onPress={() => {                  setText('');                  onClear?.();                  inputRef.current?.focus();                }}                style={[                  styles.clearDot,                  { width: s.clear, height: s.clear, borderRadius: s.clear / 2, backgroundColor: alpha(colors.mutedForeground, 0.22) },                ]}              >                <Svg width={s.clear / 2} height={s.clear / 2} viewBox="0 0 24 24" fill="none">                  <Path d="M6 6l12 12M18 6L6 18" stroke={colors.foreground} strokeWidth={3} strokeLinecap="round" />                </Svg>              </Pressable>            </Animated.View>            {trailing ? (              <Animated.View style={[styles.trailing, { paddingRight: s.end - 2, opacity: openN }]} pointerEvents={open ? 'auto' : 'none'}>                {trailing}              </Animated.View>            ) : (              <View style={{ width: s.end }} />            )}            {minimal && !open ? (              <Pressable                accessibilityRole="button"                accessibilityLabel={placeholder}                onPress={expand}                disabled={!editable}                style={StyleSheet.absoluteFill}              />            ) : null}          </Animated.View>        </Animated.View>         {cancelEnabled ? (          <>            <Text              pointerEvents="none"              onLayout={(e) => setCancelW(Math.ceil(e.nativeEvent.layout.width) + 14)}              style={[styles.cancelText, styles.measure, { fontSize: s.font }]}            >              {cancelLabel}            </Text>            <Animated.View              style={{ height: s.h, overflow: 'hidden', width: cancelJS.interpolate({ inputRange: [0, 1], outputRange: [0, cancelW] }) }}            >              <Animated.View                style={[                  styles.cancel,                  {                    width: cancelW,                    opacity: cancelN,                    transform: [{ translateX: cancelN.interpolate({ inputRange: [0, 1], outputRange: [24, 0] }) }],                  },                ]}              >                <Pressable                  hitSlop={{ top: 10, bottom: 10, left: 6, right: 10 }}                  accessibilityRole="button"                  accessibilityLabel={cancelLabel}                  onPress={cancel}                >                  <Text numberOfLines={1} style={[styles.cancelText, { color: tint, fontSize: s.font }]}>                    {cancelLabel}                  </Text>                </Pressable>              </Animated.View>            </Animated.View>          </>        ) : null}      </View>       {suggestions ? (        <SuggestionPanel open={panelOpen && open && visible.length > 0} items={visible} query={query} tint={tint} onSelect={select} />      ) : null}    </View>  );}); /** Dropdown list under the bar: drops in, rows show an icon, bolded match and a hint. */function SuggestionPanel({  open,  items,  query,  tint,  onSelect,}: {  open: boolean;  items: SearchSuggestion[];  query: string;  tint: string;  onSelect: (item: SearchSuggestion) => void;}) {  const { colors, radius } = useKit();  const [mounted, setMounted] = useState(open);  const enter = useRef(new Animated.Value(0)).current;  const last = useRef(items);  if (items.length) last.current = items;   useEffect(() => {    if (open) {      setMounted(true);      Animated.spring(enter, { toValue: 1, useNativeDriver: true, ...motion.snappy }).start();    } else {      Animated.timing(enter, { toValue: 0, duration: motion.fast, useNativeDriver: true }).start(({ finished }) => {        if (finished) setMounted(false);      });    }  }, [open, enter]);   if (!mounted) return null;  return (    <Animated.View      accessibilityRole="list"      style={[        styles.panel,        {          backgroundColor: colors.popover,          borderColor: colors.border,          borderRadius: radius.lg,          opacity: enter,          transform: [            { translateY: enter.interpolate({ inputRange: [0, 1], outputRange: [-6, 0] }) },            { scale: enter.interpolate({ inputRange: [0, 1], outputRange: [0.98, 1] }) },          ],        },      ]}    >      {last.current.map((item) => (        <Pressable          key={item.key}          accessibilityRole="button"          accessibilityLabel={item.hint ? `${item.label}, ${item.hint}` : item.label}          onPress={() => onSelect(item)}          style={({ pressed }) => [styles.option, pressed && { backgroundColor: alpha(tint, 0.1) }]}        >          <View style={[styles.optionIcon, { backgroundColor: colors.muted }]}>            <Icon name={item.icon ?? 'search'} size={15} color={item.icon === 'trending' ? tint : colors.mutedForeground} />          </View>          <Text numberOfLines={1} style={[styles.optionText, { color: colors.mutedForeground }]}>            <Highlight label={item.label} query={query} color={colors.foreground} />          </Text>          {item.hint ? (            <Text numberOfLines={1} style={[styles.hint, { color: colors.mutedForeground }]}>              {item.hint}            </Text>          ) : null}        </Pressable>      ))}    </Animated.View>  );} /** Bolds the part of `label` that matches the query (the whole label when there is no query). */function Highlight({ label, query, color }: { label: string; query: string; color: string }) {  const at = query ? label.toLowerCase().indexOf(query) : -1;  if (!query) return <Text style={{ color, fontWeight: '400' }}>{label}</Text>;  if (at < 0) return <Text>{label}</Text>;  return (    <>      {label.slice(0, at)}      <Text style={{ color, fontWeight: '400' }}>{label.slice(at, at + query.length)}</Text>      {label.slice(at + query.length)}    </>  );} function Icon({ name, size, color }: { name: 'search' | 'recent' | 'trending'; size: number; color: string }) {  const p = { stroke: color, strokeWidth: 1.8, strokeLinecap: 'round' as const, strokeLinejoin: 'round' as const, fill: 'none' };  return (    <Svg width={size} height={size} viewBox="0 0 24 24" fill="none">      {name === 'search' ? (        <>          <Circle cx={10.8} cy={10.8} r={6.6} {...p} strokeWidth={2.1} />          <Path d="M15.8 15.8 20 20" {...p} strokeWidth={2.1} />        </>      ) : name === 'recent' ? (        <>          <Path d="M4.6 12a7.6 7.6 0 1 0 2.2-5.4" {...p} />          <Path d="M4.5 4.4v3.3h3.3" {...p} />          <Path d="M12 8.2V12l2.6 1.7" {...p} />        </>      ) : (        <>          <Path d="M3.5 16.5 9.2 10.8l3.6 3.6L20.5 6.7" {...p} />          <Path d="M15.3 6.7h5.2v5.2" {...p} />        </>      )}    </Svg>  );} const styles = StyleSheet.create({  root: { alignSelf: 'stretch' },  disabled: { opacity: 0.5 },  row: { flexDirection: 'row', alignItems: 'center' },  flex: { flex: 1 },  halo: { position: 'absolute', top: -4, left: -4, right: -4, bottom: -4 },  field: { flex: 1, flexDirection: 'row', alignItems: 'center', borderWidth: 1.5, overflow: 'hidden' },  slot: { height: '100%', alignItems: 'center', justifyContent: 'center' },  center: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, alignItems: 'center', justifyContent: 'center' },  input: { paddingVertical: 0, paddingLeft: 0, paddingRight: 4 },  clear: { alignItems: 'center', justifyContent: 'center' },  clearDot: { alignItems: 'center', justifyContent: 'center' },  trailing: { paddingLeft: 2, alignItems: 'center', justifyContent: 'center' },  cancel: { position: 'absolute', left: 0, top: 0, bottom: 0, justifyContent: 'center', paddingLeft: 14 },  cancelText: { fontWeight: '400' },  measure: { position: 'absolute', opacity: 0, left: 0, top: 0 },  panel: {    marginTop: 10,    paddingVertical: 6,    borderWidth: StyleSheet.hairlineWidth * 2,    overflow: 'hidden',    shadowColor: '#000',    shadowOpacity: 0.08,    shadowRadius: 16,    shadowOffset: { width: 0, height: 8 },    elevation: 4,  },  option: { flexDirection: 'row', alignItems: 'center', gap: 12, minHeight: 44, paddingHorizontal: 12 },  optionIcon: { width: 28, height: 28, borderRadius: 14, alignItems: 'center', justifyContent: 'center' },  optionText: { flex: 1, fontSize: 15 },  hint: { fontSize: 12.5, maxWidth: 110 },});