Merge branch '3p-moderators' of github.com:bluesky-social/social-app into 3p-moderators
This commit is contained in:
@@ -49,12 +49,12 @@ const GroupContext = React.createContext<{
|
||||
setFieldValue: () => {},
|
||||
})
|
||||
|
||||
export type GroupProps = React.PropsWithChildren<{
|
||||
export type GroupProps<T = string> = React.PropsWithChildren<{
|
||||
type?: 'radio' | 'checkbox'
|
||||
values: string[]
|
||||
values: T[]
|
||||
maxSelections?: number
|
||||
disabled?: boolean
|
||||
onChange: (value: string[]) => void
|
||||
onChange: (value: T[]) => void
|
||||
label: string
|
||||
}>
|
||||
|
||||
@@ -73,7 +73,7 @@ export function useItemContext() {
|
||||
return React.useContext(ItemContext)
|
||||
}
|
||||
|
||||
export function Group({
|
||||
export function Group<ValuesType = string>({
|
||||
children,
|
||||
values: providedValues,
|
||||
onChange,
|
||||
@@ -81,13 +81,13 @@ export function Group({
|
||||
type = 'checkbox',
|
||||
maxSelections,
|
||||
label,
|
||||
}: GroupProps) {
|
||||
}: GroupProps<ValuesType>) {
|
||||
const groupRole = type === 'radio' ? 'radiogroup' : undefined
|
||||
const values = type === 'radio' ? providedValues.slice(0, 1) : providedValues
|
||||
const [maxReached, setMaxReached] = React.useState(false)
|
||||
|
||||
const setFieldValue = React.useCallback<
|
||||
(props: {name: string; value: boolean}) => void
|
||||
(props: {name: ValuesType; value: boolean}) => void
|
||||
>(
|
||||
({name, value}) => {
|
||||
if (type === 'checkbox') {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import {createSinglePathSVG} from './TEMPLATE'
|
||||
|
||||
export const Filter_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||
path: 'M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v4a1 1 0 0 1-.293.707L15 14.414V20a1 1 0 0 1-.758.97l-4 1A1 1 0 0 1 9 21v-6.586L3.293 8.707A1 1 0 0 1 3 8V4Zm2 1v2.586l5.707 5.707A1 1 0 0 1 11 14v5.72l2-.5V14a1 1 0 0 1 .293-.707L19 7.586V5H5Z',
|
||||
})
|
||||
@@ -0,0 +1,5 @@
|
||||
import {createSinglePathSVG} from './TEMPLATE'
|
||||
|
||||
export const SettingsGear2_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||
path: 'M11.1 2a1 1 0 0 0-.832.445L8.851 4.57 6.6 4.05a1 1 0 0 0-.932.268l-1.35 1.35a1 1 0 0 0-.267.932l.52 2.251-2.126 1.417A1 1 0 0 0 2 11.1v1.8a1 1 0 0 0 .445.832l2.125 1.417-.52 2.251a1 1 0 0 0 .268.932l1.35 1.35a1 1 0 0 0 .932.267l2.251-.52 1.417 2.126A1 1 0 0 0 11.1 22h1.8a1 1 0 0 0 .832-.445l1.417-2.125 2.251.52a1 1 0 0 0 .932-.268l1.35-1.35a1 1 0 0 0 .267-.932l-.52-2.251 2.126-1.417A1 1 0 0 0 22 12.9v-1.8a1 1 0 0 0-.445-.832L19.43 8.851l.52-2.251a1 1 0 0 0-.268-.932l-1.35-1.35a1 1 0 0 0-.932-.267l-2.251.52-1.417-2.126A1 1 0 0 0 12.9 2h-1.8Zm-.968 4.255L11.635 4h.73l1.503 2.255a1 1 0 0 0 1.057.42l2.385-.551.566.566-.55 2.385a1 1 0 0 0 .42 1.057L20 11.635v.73l-2.255 1.503a1 1 0 0 0-.42 1.057l.551 2.385-.566.566-2.385-.55a1 1 0 0 0-1.057.42L12.365 20h-.73l-1.503-2.255a1 1 0 0 0-1.057-.42l-2.385.551-.566-.566.55-2.385a1 1 0 0 0-.42-1.057L4 12.365v-.73l2.255-1.503a1 1 0 0 0 .42-1.057L6.123 6.69l.566-.566 2.385.55a1 1 0 0 0 1.057-.42ZM8 12a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm4-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4Z',
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import {Pressable, View} from 'react-native'
|
||||
import {View} from 'react-native'
|
||||
import {InterpretedLabelValueDefinition, LabelPreference} from '@atproto/api'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
@@ -19,10 +19,11 @@ import {useTheme, atoms as a} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {InlineLink} from '#/components/Link'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {Button, ButtonText, ButtonIcon} from '#/components/Button'
|
||||
import {ArrowTriangleBottom_Stroke2_Corner1_Rounded as ArrowTriangleBottom} from '../icons/ArrowTriangle'
|
||||
import {Button} from '#/components/Button'
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '../icons/CircleInfo'
|
||||
import {Check_Stroke2_Corner0_Rounded as Check} from '../icons/Check'
|
||||
import {SettingsGear2_Stroke2_Corner0_Rounded as Gear} from '../icons/Gear'
|
||||
import * as Toggle from '#/components/forms/Toggle'
|
||||
import {Divider} from '#/components/Divider'
|
||||
|
||||
export function ModerationLabelPref({
|
||||
labelValueDefinition,
|
||||
@@ -51,6 +52,7 @@ export function ModerationLabelPref({
|
||||
savedPref ??
|
||||
labelValueDefinition.defaultSetting ??
|
||||
'warn'
|
||||
const [selected, setSelected] = React.useState<LabelPreference[]>([pref])
|
||||
|
||||
const settingDesc = useLabelBehaviorDescription(labelValueDefinition, pref)
|
||||
const hideLabel = useLabelLongBehaviorDescription(
|
||||
@@ -81,62 +83,85 @@ export function ModerationLabelPref({
|
||||
adultOnly && !preferences?.moderationPrefs.adultContentEnabled
|
||||
const cantConfigure = isGlobalLabel || adultDisabled
|
||||
|
||||
const onSelectPref = (newPref: LabelPreference) =>
|
||||
mutate({label: identifier, visibility: newPref, labelerDid})
|
||||
const onSettingChange = React.useCallback(
|
||||
(newPrefs: LabelPreference[]) => {
|
||||
setSelected(newPrefs)
|
||||
mutate({label: identifier, visibility: newPrefs[0], labelerDid})
|
||||
},
|
||||
[mutate, labelerDid, identifier],
|
||||
)
|
||||
|
||||
const settings = [
|
||||
{
|
||||
pref: 'hide',
|
||||
label: hideLabel,
|
||||
},
|
||||
canWarn && {
|
||||
pref: 'warn',
|
||||
label: warnLabel,
|
||||
},
|
||||
{
|
||||
pref: 'ignore',
|
||||
label: ignoreLabel,
|
||||
},
|
||||
].filter(Boolean) as {
|
||||
pref: LabelPreference
|
||||
label: string
|
||||
}[]
|
||||
|
||||
return (
|
||||
<>
|
||||
<Pressable
|
||||
<Button
|
||||
onPress={() => control.open()}
|
||||
accessibilityLabel={settingDesc}
|
||||
accessibilityHint=""
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.justify_between,
|
||||
a.gap_sm,
|
||||
a.align_center,
|
||||
t.atoms.bg_contrast_25,
|
||||
a.px_lg,
|
||||
a.py_lg,
|
||||
a.rounded_sm,
|
||||
]}>
|
||||
<Text style={[a.font_bold]}>{labelStrings.name}</Text>
|
||||
|
||||
<Text
|
||||
style={[t.atoms.text_contrast_medium, a.flex_1]}
|
||||
numberOfLines={1}>
|
||||
{labelStrings.description}
|
||||
</Text>
|
||||
{!disabled && (
|
||||
label={settingDesc}
|
||||
disabled={disabled}>
|
||||
{({hovered, focused, pressed}) => (
|
||||
<View
|
||||
style={[
|
||||
{width: 100},
|
||||
a.w_full,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_end,
|
||||
a.gap_xs,
|
||||
a.rounded_2xs,
|
||||
a.justify_between,
|
||||
a.gap_sm,
|
||||
a.align_start,
|
||||
a.px_lg,
|
||||
a.py_lg,
|
||||
a.rounded_sm,
|
||||
t.atoms.bg_contrast_25,
|
||||
(hovered || focused || pressed) && [t.atoms.bg_contrast_50],
|
||||
]}>
|
||||
{!disabled && (
|
||||
<Text style={[t.atoms.text_contrast_medium, a.font_semibold]}>
|
||||
{settingDesc}
|
||||
<View style={[a.gap_xs]}>
|
||||
<Text style={[a.font_bold]}>{labelStrings.name}</Text>
|
||||
<Text
|
||||
style={[t.atoms.text_contrast_medium, a.leading_snug]}
|
||||
numberOfLines={1}>
|
||||
{labelStrings.description}
|
||||
</Text>
|
||||
)}
|
||||
{!cantConfigure && (
|
||||
<ArrowTriangleBottom
|
||||
width={8}
|
||||
fill={t.atoms.text_contrast_medium.color}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{!disabled && (
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_end,
|
||||
a.gap_xs,
|
||||
a.rounded_2xs,
|
||||
]}>
|
||||
<Text style={[t.atoms.text_contrast_medium, a.font_semibold]}>
|
||||
{settingDesc}
|
||||
</Text>
|
||||
<Gear size="sm" fill={t.atoms.text_contrast_low.color} />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</Pressable>
|
||||
</Button>
|
||||
|
||||
<Dialog.Outer control={control}>
|
||||
<Dialog.Handle />
|
||||
|
||||
<Dialog.Inner
|
||||
label={_(msg`Settings: ${labelValueDefinition.identifier}`)}
|
||||
label={_(msg`Settings for ${labelValueDefinition.identifier}`)}
|
||||
style={[a.gap_sm]}>
|
||||
<Text style={[a.text_2xl, a.font_bold, a.pb_xs, t.atoms.text]}>
|
||||
{labelStrings.name}
|
||||
@@ -144,10 +169,21 @@ export function ModerationLabelPref({
|
||||
<Text style={[a.text_md, a.pb_sm, t.atoms.text_contrast_medium]}>
|
||||
{labelStrings.description}
|
||||
</Text>
|
||||
|
||||
{cantConfigure && (
|
||||
<View style={[a.flex_row, a.gap_xs, a.align_center, a.pb_sm]}>
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.gap_xs,
|
||||
a.align_center,
|
||||
a.py_md,
|
||||
a.px_lg,
|
||||
a.rounded_sm,
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<CircleInfo size="md" fill={t.atoms.text_contrast_medium.color} />
|
||||
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||
|
||||
<Text style={[t.atoms.text_contrast_medium]}>
|
||||
{adultDisabled ? (
|
||||
<Trans>
|
||||
Adult content must be enabled to configure this label.
|
||||
@@ -167,73 +203,47 @@ export function ModerationLabelPref({
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
{disabled || cantConfigure ? (
|
||||
<Button
|
||||
label={_(msg`Close`)}
|
||||
size="large"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
onPress={() => control.close()}>
|
||||
<ButtonText style={[a.flex_1, a.text_left]}>
|
||||
<Trans>Close</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
label={hideLabel}
|
||||
size="large"
|
||||
variant="solid"
|
||||
color={pref === 'hide' ? 'primary' : 'secondary'}
|
||||
onPress={() => {
|
||||
onSelectPref('hide')
|
||||
control.close()
|
||||
}}>
|
||||
<ButtonText style={[a.flex_1, a.text_left]}>
|
||||
{hideLabel}
|
||||
</ButtonText>
|
||||
{pref === 'hide' && (
|
||||
<ButtonIcon icon={Check} position="right" />
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{canWarn && (
|
||||
<Button
|
||||
label={warnLabel}
|
||||
size="large"
|
||||
variant="solid"
|
||||
color={pref === 'warn' ? 'primary' : 'secondary'}
|
||||
onPress={() => {
|
||||
onSelectPref('warn')
|
||||
control.close()
|
||||
}}>
|
||||
<ButtonText style={[a.flex_1, a.text_left]}>
|
||||
{warnLabel}
|
||||
</ButtonText>
|
||||
{pref === 'warn' && (
|
||||
<ButtonIcon icon={Check} position="right" />
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
{!cantConfigure && (
|
||||
<Toggle.Group<LabelPreference>
|
||||
type="radio"
|
||||
values={selected}
|
||||
onChange={onSettingChange}
|
||||
label={_(
|
||||
msg`Configure filtering settings for ${labelStrings.name}`,
|
||||
)}>
|
||||
<View
|
||||
style={[
|
||||
a.rounded_md,
|
||||
a.overflow_hidden,
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
{settings.map((s, i) => (
|
||||
<>
|
||||
{i !== 0 && <Divider />}
|
||||
|
||||
<Button
|
||||
label={ignoreLabel}
|
||||
size="large"
|
||||
variant="solid"
|
||||
color={!disabled && pref === 'ignore' ? 'primary' : 'secondary'}
|
||||
onPress={() => {
|
||||
onSelectPref('ignore')
|
||||
control.close()
|
||||
}}>
|
||||
<ButtonText style={[a.flex_1, a.text_left]}>
|
||||
{ignoreLabel}
|
||||
</ButtonText>
|
||||
{pref === 'ignore' && (
|
||||
<ButtonIcon icon={Check} position="right" />
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
<Toggle.Item name={s.pref} label={s.label}>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.gap_md,
|
||||
a.py_md,
|
||||
a.px_lg,
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<Toggle.Radio />
|
||||
<Text>{s.label}</Text>
|
||||
</View>
|
||||
</Toggle.Item>
|
||||
</>
|
||||
))}
|
||||
</View>
|
||||
</Toggle.Group>
|
||||
)}
|
||||
|
||||
<Dialog.Close />
|
||||
</Dialog.Inner>
|
||||
</Dialog.Outer>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user