eletrotupi / tcc/ frontend/components/ui/ScaleSlider.tsx24fd7a77032477adf16f02f8321f475ab2c6ea8c
4.1 KB | 182 lines Raw
1
import React, { useState } from 'react';
2
import {
3
  View,
4
  Text,
5
  StyleSheet,
6
  ViewStyle,
7
} from 'react-native';
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
  min?: number;
19
  max?: number;
20
  step?: number;
21
  minLabel?: string;
22
  maxLabel?: string;
23
  icon?: React.ReactNode;      // rich variant only
24
  showMax?: boolean;           // shows "x/10" format instead of just "x"
25
  variant?: SliderVariant;
26
  style?: ViewStyle;
27
}
28

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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