search, temp setup for matches UI
This commit is contained in:
@@ -31,7 +31,7 @@ import {
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass2'
|
||||
import {MagnifyingGlass_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass'
|
||||
import {PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon} from '#/components/icons/Person'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||
import {boostInterests, InterestTabs} from '#/components/InterestTabs'
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
useRemoveMutedWordsMutation,
|
||||
useUpsertMutedWordsMutation,
|
||||
} from '#/state/queries/preferences'
|
||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
|
||||
import {MagnifyingGlass_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass'
|
||||
import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute'
|
||||
import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person'
|
||||
import {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {atoms as a, useBreakpoints} from '#/alf'
|
||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded} from './icons/Times'
|
||||
|
||||
export function SearchError({
|
||||
title,
|
||||
@@ -14,7 +13,7 @@ export function SearchError({
|
||||
children?: React.ReactNode
|
||||
}) {
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const pal = usePalette('default')
|
||||
const t = useTheme()
|
||||
|
||||
return (
|
||||
<Layout.Content>
|
||||
@@ -27,7 +26,7 @@ export function SearchError({
|
||||
paddingVertical: 150,
|
||||
},
|
||||
]}>
|
||||
<TimesLarge_Stroke2_Corner0_Rounded width={32} fill={pal.colors.icon} />
|
||||
<XIcon width={32} style={[t.atoms.text_contrast_low]} />
|
||||
<View
|
||||
style={[
|
||||
a.align_center,
|
||||
|
||||
@@ -46,7 +46,7 @@ export function PhoneInput({
|
||||
mutationFn: async ({}: {countryCode: string; phoneNumber: string}) => {
|
||||
// get otp
|
||||
await new Promise(resolve => {
|
||||
setTimeout(resolve, 1000)
|
||||
setTimeout(resolve, 500)
|
||||
})
|
||||
},
|
||||
onSuccess: (_data, {countryCode, phoneNumber}) => {
|
||||
|
||||
@@ -4,7 +4,6 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useMutation} from '@tanstack/react-query'
|
||||
|
||||
import {wait} from '#/lib/async/wait'
|
||||
import {getPhoneCodeFromCountryCode} from '#/lib/international-telephone-codes'
|
||||
import {clamp} from '#/lib/numbers'
|
||||
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
||||
@@ -55,11 +54,10 @@ export function VerifyNumber({
|
||||
isSuccess,
|
||||
} = useMutation({
|
||||
mutationFn: async (_code: string) => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
return 'success'
|
||||
},
|
||||
onSuccess: async () => {
|
||||
await wait(2e3, () => {})
|
||||
dispatch({
|
||||
type: 'VERIFY_PHONE_NUMBER_SUCCESS',
|
||||
payload: {
|
||||
@@ -239,7 +237,6 @@ function OTPStatus({
|
||||
text = _(msg`Phone verified`)
|
||||
textColor = t.palette.positive_500
|
||||
} else if (isPending) {
|
||||
Icon = Loader
|
||||
text = _(msg`Verifying...`)
|
||||
} else if (error) {
|
||||
Icon = WarningIcon
|
||||
|
||||
@@ -1,22 +1,36 @@
|
||||
import {useMemo, useState} from 'react'
|
||||
import {useMemo, useRef, useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import * as SMS from 'expo-sms'
|
||||
import {type ModerationOpts} from '@atproto/api'
|
||||
import {msg, Plural, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {wait} from '#/lib/async/wait'
|
||||
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {
|
||||
updateProfileShadow,
|
||||
useProfileShadow,
|
||||
} from '#/state/cache/profile-shadow'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useSession} from '#/state/session'
|
||||
import {List} from '#/view/com/util/List'
|
||||
import {useProfilesQuery} from '#/state/queries/profile'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {List, type ListMethods} from '#/view/com/util/List'
|
||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {bulkWriteFollows} from '#/screens/Onboarding/util'
|
||||
import {atoms as a, useGutters, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {SearchInput} from '#/components/forms/SearchInput'
|
||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||
import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
|
||||
import {Envelope_Stroke2_Corner0_Rounded as EnvelopeIcon} from '#/components/icons/Envelope'
|
||||
import {MagnifyingGlassX_Stroke2_Corner0_Rounded_Large as SearchFailedIcon} from '#/components/icons/MagnifyingGlass'
|
||||
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ListFooter} from '#/components/Lists'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import * as ProfileCard from '#/components/ProfileCard'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {Text} from '#/components/Typography'
|
||||
@@ -26,6 +40,7 @@ import {type Action, type Contact, type State} from '../state'
|
||||
type Item =
|
||||
| {
|
||||
type: 'matches header'
|
||||
count: number
|
||||
}
|
||||
| {
|
||||
type: 'match'
|
||||
@@ -43,6 +58,7 @@ type Item =
|
||||
}
|
||||
| {
|
||||
type: 'search empty state'
|
||||
query: string
|
||||
}
|
||||
// we could use Contacts.presentAccessPickerAsync() if we get limited permissions?
|
||||
// | {
|
||||
@@ -51,6 +67,7 @@ type Item =
|
||||
|
||||
export function ViewMatches({
|
||||
state,
|
||||
dispatch,
|
||||
}: {
|
||||
state: Extract<State, {step: '4: view matches'}>
|
||||
dispatch: React.Dispatch<Action>
|
||||
@@ -58,37 +75,165 @@ export function ViewMatches({
|
||||
const {_} = useLingui()
|
||||
const gutter = useGutters([0, 'wide'])
|
||||
const moderationOpts = useModerationOpts()
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const listRef = useRef<ListMethods>(null)
|
||||
|
||||
// TEMP!!!
|
||||
const {data: profiles} = useProfilesQuery({
|
||||
handles: ['pfrazee.com', 'internet.bsky.social', 'darrin.bsky.team'],
|
||||
})
|
||||
state.matches = profiles?.profiles?.map(profile => ({profile})) ?? []
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
const {
|
||||
state: searchFocused,
|
||||
onIn: onFocus,
|
||||
onOut: onBlur,
|
||||
} = useInteractionState()
|
||||
|
||||
const followableDids = state.matches
|
||||
.map(match => match.profile.did)
|
||||
.filter(did => !state.dismissedMatches.includes(did))
|
||||
const [didFollowAll, setDidFollowAll] = useState(followableDids.length === 0)
|
||||
|
||||
const {mutate: followAll, isPending: isFollowingAll} = useMutation({
|
||||
mutationFn: async () => {
|
||||
for (const did of followableDids) {
|
||||
updateProfileShadow(queryClient, did, {
|
||||
followingUri: 'pending',
|
||||
})
|
||||
}
|
||||
|
||||
const uris = await wait(500, bulkWriteFollows(agent, followableDids))
|
||||
|
||||
for (const did of followableDids) {
|
||||
const uri = uris.get(did)
|
||||
updateProfileShadow(queryClient, did, {
|
||||
followingUri: uri,
|
||||
})
|
||||
}
|
||||
return followableDids
|
||||
},
|
||||
onSuccess: () => {
|
||||
setDidFollowAll(true)
|
||||
Toast.show(_(msg`All friends followed!`), {type: 'success'})
|
||||
},
|
||||
onError: _err => {
|
||||
Toast.show(_(msg`Failed to follow all your friends, please try again`), {
|
||||
type: 'error',
|
||||
})
|
||||
for (const did of followableDids) {
|
||||
updateProfileShadow(queryClient, did, {
|
||||
followingUri: undefined,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const items = useMemo(() => {
|
||||
const all: Item[] = []
|
||||
|
||||
if (state.matches.length > 0) {
|
||||
all.push({type: 'matches header'})
|
||||
if (searchFocused || search.length > 0) {
|
||||
for (const match of state.matches) {
|
||||
const profile = match.profile
|
||||
all.push({type: 'match', profile})
|
||||
}
|
||||
all.push({type: 'contacts header'})
|
||||
} else {
|
||||
all.push({type: 'no matches header'})
|
||||
}
|
||||
|
||||
for (const contact of state.contacts) {
|
||||
all.push({type: 'contact', contact})
|
||||
if (state.dismissedMatches.includes(profile.did)) continue
|
||||
|
||||
if (
|
||||
search.length === 0 ||
|
||||
(profile.displayName ?? '')
|
||||
.toLocaleLowerCase()
|
||||
.includes(search.toLocaleLowerCase()) ||
|
||||
profile.handle
|
||||
.toLocaleLowerCase()
|
||||
.includes(search.toLocaleLowerCase())
|
||||
) {
|
||||
all.push({type: 'match', profile})
|
||||
}
|
||||
}
|
||||
|
||||
for (const contact of state.contacts) {
|
||||
if (
|
||||
search.length === 0 ||
|
||||
[contact.firstName, contact.lastName]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
.toLocaleLowerCase()
|
||||
.includes(search.toLocaleLowerCase())
|
||||
) {
|
||||
all.push({type: 'contact', contact})
|
||||
}
|
||||
}
|
||||
|
||||
if (all.length === 0) {
|
||||
all.push({type: 'search empty state', query: search})
|
||||
}
|
||||
} else {
|
||||
const matches = state.matches.filter(
|
||||
match => !state.dismissedMatches.includes(match.profile.did),
|
||||
)
|
||||
|
||||
if (matches.length > 0) {
|
||||
all.push({type: 'matches header', count: matches.length})
|
||||
for (const match of matches) {
|
||||
const profile = match.profile
|
||||
all.push({type: 'match', profile})
|
||||
}
|
||||
all.push({type: 'contacts header'})
|
||||
} else {
|
||||
all.push({type: 'no matches header'})
|
||||
}
|
||||
|
||||
for (const contact of state.contacts) {
|
||||
all.push({type: 'contact', contact})
|
||||
}
|
||||
}
|
||||
|
||||
return all
|
||||
}, [state.matches, state.contacts])
|
||||
}, [
|
||||
state.matches,
|
||||
state.contacts,
|
||||
state.dismissedMatches,
|
||||
search,
|
||||
searchFocused,
|
||||
])
|
||||
|
||||
const numMatches = state.matches.length
|
||||
const {mutate: dimissMatch} = useMutation({
|
||||
mutationFn: async (_did: string) => {
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
},
|
||||
onMutate: did => {
|
||||
dispatch({type: 'DISMISS_MATCH', payload: {did}})
|
||||
},
|
||||
onError: (err, did) => {
|
||||
dispatch({type: 'DISMISS_MATCH_FAILED', payload: {did}})
|
||||
if (isNetworkError(err)) {
|
||||
Toast.show(
|
||||
_(
|
||||
msg`Failed to hide suggestion, please check your internet connection`,
|
||||
),
|
||||
{type: 'error'},
|
||||
)
|
||||
} else {
|
||||
logger.error('Dismissing match failed', {safeMessage: err})
|
||||
Toast.show(
|
||||
_(msg`An error occurred while hiding suggestion. ${cleanError(err)}`),
|
||||
{type: 'error'},
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const renderItem = ({item}: {item: Item}) => {
|
||||
switch (item.type) {
|
||||
case 'match':
|
||||
return (
|
||||
<MatchItem profile={item.profile} moderationOpts={moderationOpts} />
|
||||
<MatchItem
|
||||
profile={item.profile}
|
||||
moderationOpts={moderationOpts}
|
||||
onRemoveSuggestion={dimissMatch}
|
||||
/>
|
||||
)
|
||||
case 'contact':
|
||||
return <ContactItem contact={item.contact} />
|
||||
@@ -97,17 +242,27 @@ export function ViewMatches({
|
||||
<Header
|
||||
titleText={
|
||||
<Plural
|
||||
value={numMatches}
|
||||
value={item.count}
|
||||
one="# friend found!"
|
||||
other="# friends found!"
|
||||
/>
|
||||
}>
|
||||
{numMatches > 1 && (
|
||||
{item.count > 1 && (
|
||||
<Button
|
||||
label={_(msg`Follow all`)}
|
||||
size="small"
|
||||
color="primary_subtle">
|
||||
<ButtonIcon icon={PlusIcon} />
|
||||
color="primary_subtle"
|
||||
onPress={() => followAll()}
|
||||
disabled={isFollowingAll || didFollowAll}>
|
||||
<ButtonIcon
|
||||
icon={
|
||||
isFollowingAll
|
||||
? Loader
|
||||
: !didFollowAll
|
||||
? PlusIcon
|
||||
: CheckIcon
|
||||
}
|
||||
/>
|
||||
<ButtonText>
|
||||
<Trans>Follow all</Trans>
|
||||
</ButtonText>
|
||||
@@ -127,10 +282,12 @@ export function ViewMatches({
|
||||
/>
|
||||
)
|
||||
case 'search empty state':
|
||||
return null
|
||||
return <SearchEmptyState query={item.query} />
|
||||
}
|
||||
}
|
||||
|
||||
const isEmpty = items?.[0]?.type === 'search empty state'
|
||||
|
||||
return (
|
||||
<View style={[a.h_full]}>
|
||||
<Layout.Header.Outer noBottomBorder>
|
||||
@@ -138,31 +295,60 @@ export function ViewMatches({
|
||||
<Layout.Header.Content />
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
<View style={[gutter, a.mb_xs]}>
|
||||
<View style={[gutter, a.mb_md]}>
|
||||
<SearchInput
|
||||
placeholder={_(msg`Search contacts`)}
|
||||
value={search}
|
||||
onChangeText={setSearch}
|
||||
onFocus={() => {
|
||||
onFocus()
|
||||
listRef.current?.scrollToOffset({offset: 0, animated: false})
|
||||
}}
|
||||
onBlur={() => {
|
||||
onBlur()
|
||||
listRef.current?.scrollToOffset({offset: 0, animated: false})
|
||||
}}
|
||||
onChangeText={text => {
|
||||
setSearch(text)
|
||||
listRef.current?.scrollToOffset({offset: 0, animated: false})
|
||||
}}
|
||||
onClearText={() => setSearch('')}
|
||||
/>
|
||||
</View>
|
||||
<List
|
||||
ref={listRef}
|
||||
data={items}
|
||||
renderItem={renderItem}
|
||||
ListFooterComponent={<ListFooter />}
|
||||
ListFooterComponent={!isEmpty ? <ListFooter /> : null}
|
||||
keyExtractor={keyExtractor}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function keyExtractor(item: Item) {
|
||||
switch (item.type) {
|
||||
case 'contact':
|
||||
return item.contact.id
|
||||
case 'match':
|
||||
return item.profile.did
|
||||
default:
|
||||
return item.type
|
||||
}
|
||||
}
|
||||
|
||||
function MatchItem({
|
||||
profile,
|
||||
moderationOpts,
|
||||
onRemoveSuggestion,
|
||||
}: {
|
||||
profile: bsky.profile.AnyProfileView
|
||||
moderationOpts?: ModerationOpts
|
||||
onRemoveSuggestion: (did: string) => void
|
||||
}) {
|
||||
const gutter = useGutters([0, 'wide'])
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const shadow = useProfileShadow(profile)
|
||||
|
||||
if (!moderationOpts) return null
|
||||
|
||||
@@ -179,6 +365,17 @@ function MatchItem({
|
||||
moderationOpts={moderationOpts}
|
||||
logContext="SyncContacts"
|
||||
/>
|
||||
{!shadow.viewer?.following && (
|
||||
<Button
|
||||
color="secondary"
|
||||
variant="ghost"
|
||||
label={_(msg`Remove suggestion`)}
|
||||
onPress={() => onRemoveSuggestion(profile.did)}
|
||||
hoverStyle={[a.bg_transparent, {opacity: 0.5}]}
|
||||
hitSlop={8}>
|
||||
<ButtonIcon icon={XIcon} />
|
||||
</Button>
|
||||
)}
|
||||
</ProfileCard.Header>
|
||||
</View>
|
||||
)
|
||||
@@ -295,3 +492,31 @@ function Header({
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function SearchEmptyState({query}: {query: string}) {
|
||||
const t = useTheme()
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.flex_col,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
a.gap_lg,
|
||||
a.pt_5xl,
|
||||
a.px_5xl,
|
||||
]}>
|
||||
<SearchFailedIcon width={64} style={[t.atoms.text_contrast_low]} />
|
||||
<Text
|
||||
style={[
|
||||
a.text_md,
|
||||
a.leading_snug,
|
||||
t.atoms.text_contrast_medium,
|
||||
a.text_center,
|
||||
]}>
|
||||
<Trans>No contacts with the name “{query}” found</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@ export type State =
|
||||
step: '4: view matches'
|
||||
contacts: Contact[]
|
||||
matches: Match[]
|
||||
// rather than mutating `matches`, we keep track of dismissed matches
|
||||
// so we can roll back optimistic updates
|
||||
dismissedMatches: string[]
|
||||
}
|
||||
|
||||
export type Action =
|
||||
@@ -72,6 +75,12 @@ export type Action =
|
||||
did: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: 'DISMISS_MATCH_FAILED'
|
||||
payload: {
|
||||
did: string
|
||||
}
|
||||
}
|
||||
|
||||
function reducer(state: State, action: Action): State {
|
||||
switch (action.type) {
|
||||
@@ -118,14 +127,25 @@ function reducer(state: State, action: Action): State {
|
||||
step: '4: view matches',
|
||||
contacts: state.contacts ?? [],
|
||||
matches: action.payload.matches,
|
||||
dismissedMatches: [],
|
||||
}
|
||||
}
|
||||
case 'DISMISS_MATCH': {
|
||||
assertCurrentStep(state, '4: view matches')
|
||||
return {
|
||||
...state,
|
||||
matches: state.matches.filter(
|
||||
match => match.profile.did !== action.payload.did,
|
||||
dismissedMatches: [
|
||||
...new Set(state.dismissedMatches),
|
||||
action.payload.did,
|
||||
],
|
||||
}
|
||||
}
|
||||
case 'DISMISS_MATCH_FAILED': {
|
||||
assertCurrentStep(state, '4: view matches')
|
||||
return {
|
||||
...state,
|
||||
dismissedMatches: state.dismissedMatches.filter(
|
||||
did => did !== action.payload.did,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import * as Dialog from '#/components/Dialog'
|
||||
import * as TextField from '#/components/forms/TextField'
|
||||
import {useThrottledValue} from '#/components/hooks/useThrottledValue'
|
||||
import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow'
|
||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
|
||||
import {MagnifyingGlass_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass'
|
||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||
|
||||
export function GifSelectDialog({
|
||||
|
||||
@@ -25,7 +25,7 @@ import {Button, ButtonIcon} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {canBeMessaged} from '#/components/dms/util'
|
||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
|
||||
import {MagnifyingGlass_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||
import * as ProfileCard from '#/components/ProfileCard'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
@@ -8,7 +8,7 @@ import {isNative} from '#/platform/detection'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
import * as TextField from '#/components/forms/TextField'
|
||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlassIcon} from '#/components/icons/MagnifyingGlass2'
|
||||
import {MagnifyingGlass_Stroke2_Corner0_Rounded as MagnifyingGlassIcon} from '#/components/icons/MagnifyingGlass'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||
|
||||
type SearchInputProps = Omit<TextField.InputProps, 'label'> & {
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
import {createSinglePathSVG} from './TEMPLATE'
|
||||
|
||||
export const MagnifyingGlass_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||
path: 'M11 5a6 6 0 1 0 0 12 6 6 0 0 0 0-12Zm-8 6a8 8 0 1 1 14.32 4.906l3.387 3.387a1 1 0 0 1-1.414 1.414l-3.387-3.387A8 8 0 0 1 3 11Z',
|
||||
})
|
||||
|
||||
export const MagnifyingGlass_Filled_Stroke2_Corner0_Rounded =
|
||||
createSinglePathSVG({
|
||||
path: 'M5 11a6 6 0 1 1 12 0 6 6 0 0 1-12 0Zm6-8a8 8 0 1 0 4.906 14.32l3.387 3.387a1 1 0 0 0 1.414-1.414l-3.387-3.387A8 8 0 0 0 11 3Zm4 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z',
|
||||
})
|
||||
|
||||
export const MagnifyingGlassX_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||
path: 'M20 12a8 8 0 1 0-8 8c2.204 0 4.2-.89 5.648-2.333A7.97 7.97 0 0 0 20 12Zm-5.707-3.707a1 1 0 1 1 1.414 1.414L13.414 12l2.293 2.293a1 1 0 1 1-1.414 1.414L12 13.414l-2.293 2.293a1 1 0 1 1-1.414-1.414L10.586 12 8.293 9.707a1 1 0 1 1 1.414-1.414L12 10.586l2.293-2.293ZM22 12a9.96 9.96 0 0 1-2.269 6.34l2.891 2.89a1 1 0 1 1-1.414 1.415l-2.893-2.893A9.96 9.96 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10Z',
|
||||
})
|
||||
|
||||
export const MagnifyingGlassX_Stroke2_Corner0_Rounded_Large =
|
||||
createSinglePathSVG({
|
||||
viewBox: '0 0 64 64',
|
||||
path: 'M55 32C55 19.298 44.703 9 32 9S9 19.298 9 32s10.298 23 23 23a22.93 22.93 0 0 0 16.235-6.708A22.93 22.93 0 0 0 55 32Zm-15.707-8.707a1 1 0 1 1 1.414 1.414L33.414 32l7.293 7.293a1 1 0 1 1-1.414 1.414L32 33.414l-7.293 7.293a1 1 0 1 1-1.414-1.414L30.586 32l-7.293-7.293a1 1 0 1 1 1.414-1.414L32 30.586l7.293-7.293ZM57 32a24.9 24.9 0 0 1-6.66 16.985l8.808 8.808a1 1 0 0 1-1.414 1.414l-8.81-8.81A24.9 24.9 0 0 1 32 57C18.193 57 7 45.807 7 32S18.193 7 32 7s25 11.193 25 25Z',
|
||||
})
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import {createSinglePathSVG} from './TEMPLATE'
|
||||
|
||||
export const MagnifyingGlass2_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||
path: 'M11 5a6 6 0 1 0 0 12 6 6 0 0 0 0-12Zm-8 6a8 8 0 1 1 14.32 4.906l3.387 3.387a1 1 0 0 1-1.414 1.414l-3.387-3.387A8 8 0 0 1 3 11Z',
|
||||
})
|
||||
@@ -10,7 +10,7 @@ import {atoms as a, native, useTheme, type ViewStyleProp} from '#/alf'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
import * as FeedCard from '#/components/FeedCard'
|
||||
import {sizes as iconSizes} from '#/components/icons/common'
|
||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass2'
|
||||
import {MagnifyingGlass_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass'
|
||||
import {Link} from '#/components/Link'
|
||||
import {Text, type TextProps} from '#/components/Typography'
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/Do
|
||||
import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag'
|
||||
import {ListSparkle_Stroke2_Corner0_Rounded as List} from '#/components/icons/ListSparkle'
|
||||
import {Live_Stroke2_Corner0_Rounded as LiveIcon} from '#/components/icons/Live'
|
||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass2'
|
||||
import {MagnifyingGlass_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass'
|
||||
import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute'
|
||||
import {PeopleRemove2_Stroke2_Corner0_Rounded as UserMinus} from '#/components/icons/PeopleRemove2'
|
||||
import {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
|
||||
import {MagnifyingGlass_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass'
|
||||
import * as Menu from '#/components/Menu'
|
||||
import {Text} from '#/components/Typography'
|
||||
// import {useDialogStateControlContext} from '#/state/dialogs'
|
||||
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
HomeOpen_Stoke2_Corner0_Rounded as Home,
|
||||
} from '#/components/icons/HomeOpen'
|
||||
import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled} from '#/components/icons/MagnifyingGlass'
|
||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass2'
|
||||
import {MagnifyingGlass_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass'
|
||||
import {
|
||||
Message_Stroke2_Corner0_Rounded as Message,
|
||||
Message_Stroke2_Corner0_Rounded_Filled as MessageFilled,
|
||||
|
||||
@@ -44,7 +44,7 @@ import {
|
||||
HomeOpen_Stoke2_Corner0_Rounded as Home,
|
||||
} from '#/components/icons/HomeOpen'
|
||||
import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled} from '#/components/icons/MagnifyingGlass'
|
||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass2'
|
||||
import {MagnifyingGlass_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass'
|
||||
import {
|
||||
Message_Stroke2_Corner0_Rounded as Message,
|
||||
Message_Stroke2_Corner0_Rounded_Filled as MessageFilled,
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
HomeOpen_Stoke2_Corner0_Rounded as Home,
|
||||
} from '#/components/icons/HomeOpen'
|
||||
import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled} from '#/components/icons/MagnifyingGlass'
|
||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass2'
|
||||
import {MagnifyingGlass_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass'
|
||||
import {
|
||||
Message_Stroke2_Corner0_Rounded as Message,
|
||||
Message_Stroke2_Corner0_Rounded_Filled as MessageFilled,
|
||||
|
||||
@@ -56,7 +56,7 @@ import {
|
||||
HomeOpen_Stoke2_Corner0_Rounded as Home,
|
||||
} from '#/components/icons/HomeOpen'
|
||||
import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled} from '#/components/icons/MagnifyingGlass'
|
||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass2'
|
||||
import {MagnifyingGlass_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass'
|
||||
import {
|
||||
Message_Stroke2_Corner0_Rounded as Message,
|
||||
Message_Stroke2_Corner0_Rounded_Filled as MessageFilled,
|
||||
|
||||
Reference in New Issue
Block a user