eletrotupi / tcc/ frontend/components/ui/ScaleSlider.tsxa812da89fb85271a569adfcc0b45998ddd699b40
4.2 KB | 184 lines Raw
1
import React, { useState } 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 secondaryColor = useThemeColor({}, 'textSecondary');
47
  const tintColor = useThemeColor({}, 'tint');
48
  const trackingColor = useThemeColor({}, 'sliderTracking');
49
  const sliderLabelsColor = useThemeColor({}, 'sliderLabels');
50

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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