76 lines
2.6 KiB
TypeScript
76 lines
2.6 KiB
TypeScript
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||
|
||
import { colors, radius, space, text } from '@/theme/tokens';
|
||
|
||
type Option<T extends string> = { wert: T; titel: string; erklaerung: string };
|
||
|
||
type Props<T extends string> = {
|
||
label: string;
|
||
optionen: Option<T>[];
|
||
wert: T;
|
||
aufWahl: (w: T) => void;
|
||
};
|
||
|
||
/**
|
||
* Auswahl mit Erklärung je Option statt eines nackten Schalters.
|
||
*
|
||
* Bewusst so: „Zweck" und „Startwerte" beim Ordner entscheiden darüber, woraus
|
||
* später generiert wird – wer sie nicht versteht, baut sich einen Ordner, der
|
||
* nicht das tut, was er erwartet (konzept-bilder-feed.md §9).
|
||
*/
|
||
export function Wahl<T extends string>({ label, optionen, wert, aufWahl }: Props<T>) {
|
||
return (
|
||
<View style={s.block}>
|
||
<Text style={s.label}>{label}</Text>
|
||
<View style={s.optionen}>
|
||
{optionen.map((o) => {
|
||
const aktiv = o.wert === wert;
|
||
return (
|
||
<Pressable
|
||
key={o.wert}
|
||
onPress={() => aufWahl(o.wert)}
|
||
accessibilityRole="radio"
|
||
accessibilityState={{ selected: aktiv }}
|
||
style={({ pressed }) => [s.opt, aktiv ? s.optAktiv : null, pressed ? s.gedrueckt : null]}>
|
||
<View style={s.kopf}>
|
||
<View style={[s.punkt, aktiv ? s.punktAktiv : null]}>
|
||
{aktiv ? <View style={s.punktKern} /> : null}
|
||
</View>
|
||
<Text style={[s.titel, aktiv ? s.titelAktiv : null]}>{o.titel}</Text>
|
||
</View>
|
||
<Text style={s.erklaerung}>{o.erklaerung}</Text>
|
||
</Pressable>
|
||
);
|
||
})}
|
||
</View>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
const s = StyleSheet.create({
|
||
block: { gap: space.sm },
|
||
label: { ...text.label, color: colors.mut },
|
||
optionen: { gap: space.sm },
|
||
opt: {
|
||
backgroundColor: colors.surface,
|
||
borderColor: colors.border,
|
||
borderWidth: StyleSheet.hairlineWidth,
|
||
borderRadius: radius.md,
|
||
padding: space.lg,
|
||
gap: space.sm,
|
||
},
|
||
optAktiv: { borderColor: colors.accent, backgroundColor: 'rgba(255,92,57,0.08)' },
|
||
gedrueckt: { opacity: 0.8 },
|
||
kopf: { flexDirection: 'row', alignItems: 'center', gap: space.md },
|
||
punkt: {
|
||
width: 18, height: 18, borderRadius: 9,
|
||
borderColor: colors.borderStrong, borderWidth: 1.5,
|
||
alignItems: 'center', justifyContent: 'center',
|
||
},
|
||
punktAktiv: { borderColor: colors.accent },
|
||
punktKern: { width: 9, height: 9, borderRadius: 5, backgroundColor: colors.accent },
|
||
titel: { ...text.body, fontWeight: '600', color: colors.txt },
|
||
titelAktiv: { color: colors.txt },
|
||
erklaerung: { ...text.label, color: colors.mut, lineHeight: 18 },
|
||
});
|