| 1 | import React from 'react';
|
| 2 | import {
|
| 3 | View,
|
| 4 | StyleSheet,
|
| 5 | Pressable,
|
| 6 | ViewStyle,
|
| 7 | TextStyle,
|
| 8 | } from 'react-native';
|
| 9 | import { Text } from '@/components/ui/Text'
|
| 10 | import { Row, Between } from '@/components/ui/LayoutHelpers';
|
| 11 |
|
| 12 | import { Colors, Typography, Spacing } from '@/constants/theme';
|
| 13 | import { useThemeColor } from '@/hooks/use-theme-color';
|
| 14 |
|
| 15 | interface SectionProps {
|
| 16 | children: React.ReactNode;
|
| 17 | style?: ViewStyle;
|
| 18 | }
|
| 19 |
|
| 20 | export const Section: React.FC<SectionProps> = ({ children, style }) => {
|
| 21 | const styles = StyleSheet.create({
|
| 22 | section: {
|
| 23 | marginBottom: Spacing.sectionGap,
|
| 24 | },
|
| 25 | });
|
| 26 | return <View style={[styles.section, style]}>{children}</View>;
|
| 27 | };
|
| 28 |
|
| 29 | interface SectionHeaderProps {
|
| 30 | title: string;
|
| 31 | subtitle?: string;
|
| 32 | info?: string;
|
| 33 | actionLabel?: string;
|
| 34 | onActionPress?: () => void;
|
| 35 | style?: ViewStyle;
|
| 36 | variant?: 'default' | 'subtle';
|
| 37 | testID?: string;
|
| 38 | }
|
| 39 |
|
| 40 | // XXX: A Section must either have a label as a pill/informative thing
|
| 41 | // or it should be a link
|
| 42 | export const SectionHeader: React.FC<SectionHeaderProps> = ({
|
| 43 | title,
|
| 44 | subtitle,
|
| 45 | info,
|
| 46 | actionLabel,
|
| 47 | onActionPress,
|
| 48 | variant,
|
| 49 | style,
|
| 50 | testID,
|
| 51 | }) => {
|
| 52 | const textColor = useThemeColor({}, 'text');
|
| 53 | const tintColor = useThemeColor({}, 'tint');
|
| 54 | const textSecondaryColor = useThemeColor({}, 'textSecondary')
|
| 55 |
|
| 56 | const getTextColor = () => {
|
| 57 | switch (variant) {
|
| 58 | case 'subtle':
|
| 59 | return textSecondaryColor;
|
| 60 | default:
|
| 61 | return textColor;
|
| 62 | }
|
| 63 | };
|
| 64 |
|
| 65 | const getFont = (): TextStyle => {
|
| 66 | switch (variant) {
|
| 67 | case 'subtle':
|
| 68 | return {
|
| 69 | fontSize: 11,
|
| 70 | fontWeight: '600',
|
| 71 | letterSpacing: 0.8,
|
| 72 | color: Colors.light.textSecondary,
|
| 73 | textTransform: 'uppercase',
|
| 74 | };
|
| 75 | default:
|
| 76 | return {
|
| 77 | fontSize: Typography.headlineMd.fontSize,
|
| 78 | fontWeight: Typography.headlineMd.fontWeight,
|
| 79 | };
|
| 80 | }
|
| 81 | }
|
| 82 |
|
| 83 | const styles = StyleSheet.create({
|
| 84 | headerContainer: {
|
| 85 | marginBottom: variant === 'subtle' ? 10 : (Spacing.cardGap * 2),
|
| 86 | },
|
| 87 | title: {
|
| 88 | color: getTextColor(),
|
| 89 | ...getFont(),
|
| 90 | },
|
| 91 | subtitle: {
|
| 92 | fontSize: 14,
|
| 93 | color: textSecondaryColor,
|
| 94 | lineHeight: 20,
|
| 95 | marginTop: 4
|
| 96 | },
|
| 97 | actionButton: {
|
| 98 | padding: 4,
|
| 99 | },
|
| 100 | actionText: {
|
| 101 | fontSize: Typography.bodyMd.fontSize,
|
| 102 | fontWeight: '600',
|
| 103 | color: tintColor,
|
| 104 | },
|
| 105 | });
|
| 106 |
|
| 107 | // TODO: Rework these when we finally have chips/badges
|
| 108 | return (
|
| 109 | <View style={[styles.headerContainer, style]}>
|
| 110 | <Between>
|
| 111 | <Text style={styles.title} testID={testID}>
|
| 112 | {title}
|
| 113 | </Text>
|
| 114 |
|
| 115 | {info && !actionLabel && (
|
| 116 | <Text>
|
| 117 | {info}
|
| 118 | </Text>
|
| 119 | )}
|
| 120 |
|
| 121 | {actionLabel && !info && onActionPress && (
|
| 122 | <Pressable
|
| 123 | style={styles.actionButton}
|
| 124 | onPress={onActionPress}
|
| 125 | hitSlop={8}
|
| 126 | >
|
| 127 | <Text style={styles.actionText}>
|
| 128 | {actionLabel}
|
| 129 | </Text>
|
| 130 | </Pressable>
|
| 131 | )}
|
| 132 | </Between>
|
| 133 |
|
| 134 | {subtitle && (
|
| 135 | <Row>
|
| 136 | <Text style={styles.subtitle}>
|
| 137 | {subtitle}
|
| 138 | </Text>
|
| 139 | </Row>
|
| 140 | )}
|
| 141 | </View>
|
| 142 | );
|
| 143 | };
|
| 144 |
|