eletrotupi / tcc/ frontend/components/ui/ScaleSlider.tsxc901c35af9c84b6a29e5185545ced1129f33b8f4
4.1 KB | 183 lines Raw
1
import React from 'react';
2
import {
3
  View,
4
  StyleSheet,
5
  ViewStyle,
6
} from 'react-native';
7
import { Text } from '@/components/ui/Text';
8
import Slider from '@react-native-community/slider';
9
import { useThemeColor } from '@/hooks/use-theme-color';
10
import { Theme } from '@/constants/theme';
11

12
type SliderVariant = 'rich' | 'compact';
13

14
interface ScaleSliderProps {
15
  label: string;
16
  value: number;
17
  onValueChange: (value: number) => void;
18
  onValueFormat?: (value: number) => string;
19
  min?: number;
20
  max?: number;
21
  step?: number;
22
  minLabel?: string;
23
  maxLabel?: string;
24
  icon?: React.ReactNode;      // rich variant only
25
  showMax?: boolean;           // shows "x/10" format instead of just "x"
26
  variant?: SliderVariant;
27
  style?: ViewStyle;
28
}
29

30
export const ScaleSlider: React.FC<ScaleSliderProps> = ({
31
  label,
32
  value,
33
  onValueChange,
34
  onValueFormat,
35
  min = 0,
36
  max = 10,
37
  step = 1,
38
  minLabel,
39
  maxLabel,
40
  icon,
41
  showMax,
42
  variant = 'compact',
43
  style,
44
}) => {
45
  const textColor = useThemeColor({}, 'text');
46
  const tintColor = useThemeColor({}, 'tint');
47
  const trackingColor = useThemeColor({}, 'sliderTracking');
48
  const sliderLabelsColor = useThemeColor({}, 'sliderLabels');
49

50
  const isRich = variant === 'rich';
51

52
  const valueDisplay = showMax || !isRich
53
    ? `${value}/${max}`
54
    : `${value}`;
55

56
  return (
57
    <View style={[styles.container, style]}>
58

59
      {/* Header row */}
60
      <View style={[styles.header, isRich ? styles.headerRich : styles.headerCompact ]}>
61
        <View style={styles.labelRow}>
62
          {isRich && icon && (
63
            <View style={styles.iconWrapper}>{icon}</View>
64
          )}
65
          <Text style={[
66
            isRich ? styles.labelRich : styles.labelCompact,
67
            { color: textColor },
68
          ]}>
69
            {label}
70
          </Text>
71
        </View>
72

73
        <Text style={[
74
          isRich ? styles.valueRich : styles.valueCompact,
75
          { color: textColor },
76
        ]}>
77
          {(onValueFormat ? onValueFormat(value) : valueDisplay)}
78
        </Text>
79
      </View>
80

81
      {/* XXX: Rich variant needs some extra margin to account for icons */}
82
      <View style={{ marginRight: isRich ? -10 : -15, marginLeft: isRich ? -10 : -15 }}>
83
        {/* Slider has a glitch with the full padding of it */}
84
        <Slider
85
          style={styles.slider}
86
          minimumValue={min}
87
          maximumValue={max}
88
          step={step}
89
          value={value}
90
          onValueChange={onValueChange}
91
          minimumTrackTintColor={trackingColor}
92
          maximumTrackTintColor={trackingColor}
93
          thumbTintColor={tintColor}
94
        />
95
      </View>
96

97
      {/* Min / Max labels */}
98
      {(minLabel || maxLabel) && (
99
        <View style={styles.rangeLabels}>
100
          {minLabel && (
101
            <Text style={[styles.rangeLabel, { color: sliderLabelsColor }]}>
102
              {minLabel.toUpperCase()}
103
            </Text>
104
          )}
105
          {maxLabel && (
106
            <Text style={[styles.rangeLabel, { color: sliderLabelsColor }]}>
107
              {maxLabel.toUpperCase()}
108
            </Text>
109
          )}
110
        </View>
111
      )}
112

113
    </View>
114
  );
115
};
116

117
const styles = StyleSheet.create({
118
  container: {
119
    width: '100%',
120
  },
121
  header: {
122
    flexDirection: 'row',
123
    justifyContent: 'space-between',
124
    alignItems: 'center',
125
  },
126

127
  // Rich has bigger margin than compact
128
  headerRich: {
129
    marginBottom: 4,
130
  },
131

132
  headerCompact: {
133
    marginBottom: 1
134
  },
135

136
  labelRow: {
137
    flexDirection: 'row',
138
    alignItems: 'center',
139
    gap: 8,
140
  },
141
  iconWrapper: {
142
    justifyContent: 'center',
143
    alignItems: 'center',
144
  },
145

146
  // Rich variant
147
  labelRich: {
148
    fontSize: 14,
149
    fontWeight: 500
150
  },
151
  valueRich: {
152
    fontSize: 18,
153
    lineHeight: 28,
154
    fontWeight: 600
155
  },
156

157
  // Compact variant
158
  labelCompact: {
159
    fontSize: 12,
160
    fontWeight: 600,
161
    lineHeight: 16
162
  },
163
  valueCompact: {
164
    fontSize: 12,
165
    lineHeight: 16,
166
    fontWeight: 600,
167
    color: Theme.colors.light.textSecondary,
168
  },
169

170
  slider: {
171
    width: '100%',
172
    height: 40,
173
  },
174
  rangeLabels: {
175
    flexDirection: 'row',
176
    justifyContent: 'space-between',
177
    marginTop: -4,
178
  },
179
  rangeLabel: {
180
    ...Theme.typography.labelXs,
181
    fontWeight: 500,
182
  },
183
});