[Chat] Tweak styles in invite link dialog (#10638)

This commit is contained in:
Samuel Newman
2026-06-02 13:23:51 +03:00
committed by GitHub
parent 26e48e3876
commit 771fd5ddf6
3 changed files with 106 additions and 78 deletions
+7 -3
View File
@@ -212,9 +212,12 @@ export function Item({
) )
const highlightStyle = highlightRow const highlightStyle = highlightRow
? selected ? [
? [a.rounded_full, a.p_md, {backgroundColor: t.palette.primary_50}] a.rounded_full,
: [a.rounded_full, a.p_md] a.p_md,
hovered && t.atoms.bg_contrast_25,
selected && {backgroundColor: t.palette.primary_50},
]
: null : null
return ( return (
@@ -588,6 +591,7 @@ export function RadioWithLabel({
style={[ style={[
a.font_medium, a.font_medium,
a.flex_1, a.flex_1,
a.text_md,
a.leading_tight, a.leading_tight,
selected ? t.atoms.text : t.atoms.text_contrast_high, selected ? t.atoms.text : t.atoms.text_contrast_high,
]}> ]}>
@@ -35,13 +35,14 @@ export function EditTextButton({
a.flex_row, a.flex_row,
a.align_center, a.align_center,
a.justify_between, a.justify_between,
a.px_md, a.pl_lg,
a.pr_sm,
a.py_sm, a.py_sm,
a.gap_sm,
]}> ]}>
{typeof children === 'function' ? children(context) : children} {typeof children === 'function' ? children(context) : children}
<View <View
style={[ style={[
a.ml_sm,
a.rounded_full, a.rounded_full,
t.atoms.bg_contrast_50, t.atoms.bg_contrast_50,
{paddingHorizontal: 10, paddingVertical: 8}, {paddingHorizontal: 10, paddingVertical: 8},
@@ -1,6 +1,11 @@
import {useEffect, useState} from 'react' import {useState} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {moderateProfile, type ModerationOpts} from '@atproto/api' import {
type ChatBskyGroupDefs,
moderateProfile,
type ModerationOpts,
} from '@atproto/api'
import {plural} from '@lingui/core/macro'
import {Trans, useLingui} from '@lingui/react/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
@@ -64,14 +69,9 @@ export function InviteLinkDialog({
) )
const {joinLink} = convo.details const {joinLink} = convo.details
const enabledStatus = joinLink?.enabledStatus
const defaultStep = joinLink ? Step.MANAGE : Step.INFO const defaultStep = joinLink ? Step.MANAGE : Step.INFO
const defaultWhoCanJoin = joinLink const defaultWhoCanJoin = joinLink ? joinLinkToKey(joinLink) : 'anyone'
? [
`${joinLink.joinRule}${joinLink.requireApproval ? ':requireApproval' : ''}`,
]
: ['anyone']
const [step, setStep] = useState(defaultStep) const [step, setStep] = useState(defaultStep)
const [whoCanJoin, setWhoCanJoin] = useState(defaultWhoCanJoin) const [whoCanJoin, setWhoCanJoin] = useState(defaultWhoCanJoin)
@@ -79,13 +79,13 @@ export function InviteLinkDialog({
// Resync local state when the server-side join link rules change (mutation // Resync local state when the server-side join link rules change (mutation
// success, refetch, or change from another client). Keyed on the rule string // success, refetch, or change from another client). Keyed on the rule string
// so identity-only refetches don't bump a user mid-edit. // so identity-only refetches don't bump a user mid-edit.
const joinLinkRuleKey = joinLink const joinLinkRuleKey = joinLink ? joinLinkToKey(joinLink) : null
? `${joinLink.joinRule}${joinLink.requireApproval ? ':requireApproval' : ''}` const [prevKey, setPrevKey] = useState(joinLinkRuleKey)
: null if (joinLinkRuleKey !== prevKey) {
useEffect(() => {
setStep(joinLinkRuleKey ? Step.MANAGE : Step.INFO) setStep(joinLinkRuleKey ? Step.MANAGE : Step.INFO)
setWhoCanJoin([joinLinkRuleKey ?? 'anyone']) setWhoCanJoin(joinLinkRuleKey ?? 'anyone')
}, [joinLinkRuleKey]) setPrevKey(joinLinkRuleKey)
}
const {openComposer} = useOpenComposer() const {openComposer} = useOpenComposer()
@@ -163,22 +163,32 @@ export function InviteLinkDialog({
let content: React.ReactNode = null let content: React.ReactNode = null
let header: string | null = null let header: string | null = null
switch (step) { switch (step) {
case Step.INFO: case Step.INFO: {
header = l`Invite link` header = l`Invite link`
content = ( content = (
<> <>
<View> <View style={[a.gap_lg]}>
<Text style={[a.text_md, t.atoms.text]}> <Text style={[a.text_md, a.leading_snug]}>
<Trans> <Trans>
An invite link lets people join this group chat without being An invite link lets people join this group chat without being
added directly. You control who can use the link and whether added directly. You control who can join the chat. You can
they need your approval. You can disable the link at any time. disable the link at any time.
</Trans> </Trans>
</Text> </Text>
<Text style={[a.mt_lg, a.text_md, t.atoms.text]}> <Text style={[a.text_md, a.leading_snug]}>
<Trans> <Trans>
Your name, avatar, and the name of the group chat will be Group chats can only have a maximum of{' '}
visible to everyone. {plural(convo.details.memberLimit, {
one: '# person',
other: '# people',
})}
.
</Trans>
</Text>
<Text style={[a.text_md, a.leading_snug]}>
<Trans>
Your name, avatar, the name of the group chat, and the number of
members will be visible to everyone.
</Trans> </Trans>
</Text> </Text>
</View> </View>
@@ -199,15 +209,16 @@ export function InviteLinkDialog({
</> </>
) )
break break
case Step.GENERATE: }
header = case Step.GENERATE: {
joinLink && enabledStatus === 'enabled' const linkEnabled = joinLink?.enabledStatus === 'enabled'
? l`Update invite link` const linkHasChanged = linkEnabled && joinLinkRuleKey !== whoCanJoin
: l`Generate invite link`
header = linkEnabled ? l`Update invite link` : l`Generate invite link`
content = ( content = (
<> <>
<View> <View>
<Text style={[a.text_md, t.atoms.text]}> <Text style={[a.text_md]}>
<Trans>Choose who can join this group chat and how.</Trans> <Trans>Choose who can join this group chat and how.</Trans>
</Text> </Text>
</View> </View>
@@ -215,9 +226,9 @@ export function InviteLinkDialog({
<Toggle.Group <Toggle.Group
label={l`Who can join this group chat and how`} label={l`Who can join this group chat and how`}
type="radio" type="radio"
values={whoCanJoin} values={[whoCanJoin]}
onChange={setWhoCanJoin}> onChange={([value]) => setWhoCanJoin(value)}>
<View style={[a.gap_sm]}> <View style={[a.gap_xs]}>
{whoCanJoinOptions.map(option => ( {whoCanJoinOptions.map(option => (
<Toggle.Item <Toggle.Item
key={option.name} key={option.name}
@@ -239,18 +250,22 @@ export function InviteLinkDialog({
<View style={[a.mt_4xl]}> <View style={[a.mt_4xl]}>
<Button <Button
label={ label={
joinLink && enabledStatus === 'enabled' linkEnabled
? linkHasChanged
? l`Update invite link` ? l`Update invite link`
: l`Back`
: l`Generate invite link` : l`Generate invite link`
} }
color="primary" color={linkEnabled && !linkHasChanged ? 'secondary' : 'primary'}
size="large" size="large"
disabled={isSaving} disabled={isSaving}
onPress={() => { onPress={() => {
const parts = whoCanJoin[0].split(':') const {joinRule, requireApproval} = keyToJoinLink(whoCanJoin)
const joinRule = parts[0] if (linkEnabled) {
const requireApproval = parts[1] === 'requireApproval' if (!linkHasChanged) {
if (joinLink && enabledStatus === 'enabled') { setStep(Step.MANAGE)
return
}
editJoinLink({ editJoinLink({
joinRule, joinRule,
requireApproval, requireApproval,
@@ -263,36 +278,40 @@ export function InviteLinkDialog({
} }
}}> }}>
<ButtonText> <ButtonText>
{joinLink && enabledStatus === 'enabled' {linkEnabled
? linkHasChanged
? l`Update invite link` ? l`Update invite link`
: l`Back`
: l`Generate invite link`} : l`Generate invite link`}
</ButtonText> </ButtonText>
{linkHasChanged && (
<ButtonIcon icon={isSaving ? Loader : ArrowRightIcon} /> <ButtonIcon icon={isSaving ? Loader : ArrowRightIcon} />
)}
</Button> </Button>
</View> </View>
</> </>
) )
break break
}
case Step.MANAGE: { case Step.MANAGE: {
const linkEnabled = joinLink?.enabledStatus === 'enabled'
const linkDisabled = joinLink?.enabledStatus === 'disabled'
const joinLinkURI = joinLink?.code const joinLinkURI = joinLink?.code
? `https://bsky.app/chat/${joinLink.code}` ? `https://bsky.app/chat/${joinLink.code}`
: 'https://bsky.app/chat' : 'https://bsky.app/chat'
const createdAt = joinLink ? new Date(joinLink.createdAt) : null const createdAt = joinLink ? new Date(joinLink.createdAt) : null
const currentOptionName = joinLink const currentOption =
? `${joinLink.joinRule}${joinLink.requireApproval ? ':requireApproval' : ''}` whoCanJoinOptions.find(
: whoCanJoinOptions[0].name o => o.name === (joinLink ? joinLinkToKey(joinLink) : null),
const currentOption = whoCanJoinOptions.find( ) ?? whoCanJoinOptions[0]
o => o.name === currentOptionName,
)
const ownerValue = currentOption?.owner ?? whoCanJoinOptions[0].owner const ownerValue = currentOption?.owner ?? whoCanJoinOptions[0].owner
const memberValue = currentOption?.member ?? whoCanJoinOptions[0].member const memberValue = currentOption?.member ?? whoCanJoinOptions[0].member
header = header = linkEnabled ? l`Invite link` : l`Invite link disabled`
enabledStatus === 'enabled' ? l`Invite link` : l`Invite link disabled`
content = ( content = (
<> <>
<View style={[a.mt_lg]}> <View style={[a.mt_lg]}>
<CopyTextButton <CopyTextButton
disabled={enabledStatus === 'disabled' || !joinLink?.code} disabled={linkDisabled || !joinLink?.code}
label={l`Invite link`} label={l`Invite link`}
value={joinLinkURI}> value={joinLinkURI}>
<Text <Text
@@ -300,9 +319,7 @@ export function InviteLinkDialog({
style={[ style={[
a.mr_xs, a.mr_xs,
a.text_md, a.text_md,
enabledStatus === 'disabled' linkDisabled ? t.atoms.text_contrast_low : t.atoms.text,
? t.atoms.text_contrast_low
: t.atoms.text,
]}> ]}>
{joinLinkURI} {joinLinkURI}
</Text> </Text>
@@ -319,25 +336,25 @@ export function InviteLinkDialog({
</Text> </Text>
) : null} ) : null}
</View> </View>
{enabledStatus === 'enabled' ? ( {linkEnabled ? (
<View style={[a.mt_lg]}> <View style={[a.mt_lg]}>
{isOwner ? ( {isOwner ? (
<EditTextButton <EditTextButton
label={l`Edit link settings`} label={l`Edit link settings`}
value={ownerValue} value={ownerValue}
onPress={() => setStep(Step.GENERATE)}> onPress={() => setStep(Step.GENERATE)}>
<View style={[a.flex_1, a.mr_xs]}> <View style={[a.flex_1]}>
<Text numberOfLines={1} style={[a.text_md, t.atoms.text]}> <Text numberOfLines={1} style={[a.text_sm]}>
{ownerValue} {ownerValue}
</Text> </Text>
</View> </View>
</EditTextButton> </EditTextButton>
) : ( ) : (
<Text style={[a.text_sm, t.atoms.text]}>{memberValue}</Text> <Text style={[a.text_sm]}>{memberValue}</Text>
)} )}
</View> </View>
) : null} ) : null}
{enabledStatus === 'enabled' ? ( {linkEnabled ? (
<View style={[a.flex_row, a.justify_between, a.gap_sm, a.mt_lg]}> <View style={[a.flex_row, a.justify_between, a.gap_sm, a.mt_lg]}>
{isOwner ? ( {isOwner ? (
<StackedButton <StackedButton
@@ -350,7 +367,7 @@ export function InviteLinkDialog({
</StackedButton> </StackedButton>
) : null} ) : null}
<StackedButton <StackedButton
disabled={enabledStatus === 'disabled'} disabled={linkDisabled}
label={l`Post link`} label={l`Post link`}
icon={EditIcon} icon={EditIcon}
color="primary_subtle" color="primary_subtle"
@@ -366,7 +383,7 @@ export function InviteLinkDialog({
<Trans>Post link</Trans> <Trans>Post link</Trans>
</StackedButton> </StackedButton>
<StackedButton <StackedButton
disabled={enabledStatus === 'disabled'} disabled={linkDisabled}
label={l`Share`} label={l`Share`}
icon={ArrowShareRightIcon} icon={ArrowShareRightIcon}
color="primary_subtle" color="primary_subtle"
@@ -408,7 +425,7 @@ export function InviteLinkDialog({
) )
break break
} }
case Step.CONFIRM_DISABLE: case Step.CONFIRM_DISABLE: {
content = ( content = (
<> <>
<View style={[a.align_center, a.justify_center, a.mb_lg]}> <View style={[a.align_center, a.justify_center, a.mb_lg]}>
@@ -425,14 +442,7 @@ export function InviteLinkDialog({
]}> ]}>
<Trans>Disable this invite link?</Trans> <Trans>Disable this invite link?</Trans>
</Text> </Text>
<Text <Text style={[a.pb_2xl, a.text_center, a.text_sm, a.leading_snug]}>
style={[
a.pb_2xl,
a.text_center,
a.text_sm,
a.leading_snug,
t.atoms.text,
]}>
<Trans> <Trans>
Anyone who has it will no longer be able to join or request to Anyone who has it will no longer be able to join or request to
join. You can always create a new one. join. You can always create a new one.
@@ -466,13 +476,14 @@ export function InviteLinkDialog({
) )
break break
} }
}
if (!isOwner && (!joinLink || joinLink.enabledStatus === 'disabled')) { if (!isOwner && (!joinLink || joinLink.enabledStatus === 'disabled')) {
header = l`Invite link` header = l`Invite link`
content = ( content = (
<> <>
<View style={[a.mt_lg]}> <View style={[a.mt_lg]}>
<Text style={[a.text_sm, t.atoms.text]}> <Text style={[a.text_sm]}>
<Trans>There is no invite link for this group chat.</Trans> <Trans>There is no invite link for this group chat.</Trans>
</Text> </Text>
</View> </View>
@@ -503,9 +514,7 @@ export function InviteLinkDialog({
header={ header={
<View> <View>
<View style={[IS_WEB ? [a.px_2xl, a.pt_xl] : {paddingTop: 10}]}> <View style={[IS_WEB ? [a.px_2xl, a.pt_xl] : {paddingTop: 10}]}>
<Text style={[a.font_bold, a.text_2xl, a.mb_sm, t.atoms.text]}> <Text style={[a.font_bold, a.text_2xl, a.mb_sm]}>{header}</Text>
{header}
</Text>
</View> </View>
<Dialog.Close /> <Dialog.Close />
</View> </View>
@@ -517,3 +526,17 @@ export function InviteLinkDialog({
</Dialog.Outer> </Dialog.Outer>
) )
} }
function joinLinkToKey(joinLink: ChatBskyGroupDefs.JoinLinkView): string {
return `${joinLink.joinRule}${joinLink.requireApproval ? ':requireApproval' : ''}`
}
function keyToJoinLink(
key: string,
): Pick<ChatBskyGroupDefs.JoinLinkView, 'joinRule' | 'requireApproval'> {
const [joinRule, requireApproval] = key.split(':')
return {
joinRule,
requireApproval: requireApproval === 'requireApproval',
}
}