feat(components): TrackRow for history sessions
This commit is contained in:
1 parent
8232882977
commit
edc10826aa
2 files changed
+221
No files matched your search
@@ -0,0 +1,56 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import TestRenderer from 'react-test-renderer';
|
||||||
|
|
||||||
|
import { TrackRow } from '../components/TrackRow';
|
||||||
|
|
||||||
|
const baseProps = {
|
||||||
|
trackNumber: 3,
|
||||||
|
day: '15/06',
|
||||||
|
hour: '19:30',
|
||||||
|
title: 'Sparring',
|
||||||
|
meta: '30 min · 600 amostras',
|
||||||
|
metrics: [
|
||||||
|
{ label: 'MÉD 152' },
|
||||||
|
{ label: 'PICO 184', tone: 'hot' as const },
|
||||||
|
],
|
||||||
|
isOpen: false,
|
||||||
|
onPress: jest.fn(),
|
||||||
|
accessibilityLabel: 'Janela 3',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('TrackRow', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
baseProps.onPress.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders track number zero-padded', () => {
|
||||||
|
let tree: any;
|
||||||
|
TestRenderer.act(() => {
|
||||||
|
tree = TestRenderer.create(React.createElement(TrackRow, { ...baseProps, testID: 'row' }));
|
||||||
|
});
|
||||||
|
const json = JSON.stringify(tree.toJSON());
|
||||||
|
expect(json).toContain('T03');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders day, hour, title, and meta', () => {
|
||||||
|
let tree: any;
|
||||||
|
TestRenderer.act(() => {
|
||||||
|
tree = TestRenderer.create(React.createElement(TrackRow, { ...baseProps, testID: 'row' }));
|
||||||
|
});
|
||||||
|
const json = JSON.stringify(tree.toJSON());
|
||||||
|
expect(json).toContain('15/06');
|
||||||
|
expect(json).toContain('19:30');
|
||||||
|
expect(json).toContain('Sparring');
|
||||||
|
expect(json).toContain('30 min · 600 amostras');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders metrics labels', () => {
|
||||||
|
let tree: any;
|
||||||
|
TestRenderer.act(() => {
|
||||||
|
tree = TestRenderer.create(React.createElement(TrackRow, { ...baseProps, testID: 'row' }));
|
||||||
|
});
|
||||||
|
const json = JSON.stringify(tree.toJSON());
|
||||||
|
expect(json).toContain('MÉD 152');
|
||||||
|
expect(json).toContain('PICO 184');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
import React, { type ReactNode } from 'react';
|
||||||
|
import { Pressable, StyleSheet, Text, View, type ViewStyle } from 'react-native';
|
||||||
|
import { ChevronDown, ChevronUp } from 'lucide-react-native';
|
||||||
|
import { colors, spacing, typography } from '../theme';
|
||||||
|
|
||||||
|
type MetricTone = 'normal' | 'hot';
|
||||||
|
|
||||||
|
type Metric = {
|
||||||
|
readonly label: string;
|
||||||
|
readonly tone?: MetricTone;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
readonly trackNumber: number;
|
||||||
|
readonly day: string;
|
||||||
|
readonly hour: string;
|
||||||
|
readonly title: string;
|
||||||
|
readonly meta: string;
|
||||||
|
readonly metrics: ReadonlyArray<Metric>;
|
||||||
|
readonly isOpen: boolean;
|
||||||
|
readonly onPress: () => void;
|
||||||
|
readonly accessibilityLabel: string;
|
||||||
|
readonly children?: ReactNode;
|
||||||
|
readonly testID?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function TrackRow({
|
||||||
|
trackNumber,
|
||||||
|
day,
|
||||||
|
hour,
|
||||||
|
title,
|
||||||
|
meta,
|
||||||
|
metrics,
|
||||||
|
isOpen,
|
||||||
|
onPress,
|
||||||
|
accessibilityLabel,
|
||||||
|
children,
|
||||||
|
testID,
|
||||||
|
}: Props) {
|
||||||
|
const trackNumberLabel = `T${String(trackNumber).padStart(2, '0')}`;
|
||||||
|
const sideRailStyle: ViewStyle = {
|
||||||
|
backgroundColor: isOpen ? colors.action : colors.line,
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<Pressable
|
||||||
|
accessibilityLabel={accessibilityLabel}
|
||||||
|
accessibilityRole="button"
|
||||||
|
onPress={onPress}
|
||||||
|
style={({ pressed }) => [styles.row, pressed ? styles.pressed : null]}
|
||||||
|
testID={testID}
|
||||||
|
>
|
||||||
|
<View style={[styles.sideRail, sideRailStyle]} />
|
||||||
|
<View style={styles.trackNumberWrap}>
|
||||||
|
<Text style={styles.trackNumber}>{trackNumberLabel}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.dateBlock}>
|
||||||
|
<Text style={styles.day}>{day}</Text>
|
||||||
|
<Text style={styles.hour}>{hour}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.body}>
|
||||||
|
<Text style={styles.title}>{title}</Text>
|
||||||
|
<Text style={styles.meta}>{meta}</Text>
|
||||||
|
<View style={styles.metricLine}>
|
||||||
|
{metrics.map((metric, index) => (
|
||||||
|
<Text
|
||||||
|
key={index}
|
||||||
|
style={[
|
||||||
|
styles.metricText,
|
||||||
|
metric.tone === 'hot' ? styles.metricTextHot : null,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{metric.label}
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={styles.chevron}>
|
||||||
|
{isOpen ? (
|
||||||
|
<ChevronUp color={colors.warning} size={18} strokeWidth={2.6} />
|
||||||
|
) : (
|
||||||
|
<ChevronDown color={colors.textMuted} size={18} strokeWidth={2.6} />
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingVertical: spacing.sm,
|
||||||
|
paddingHorizontal: spacing.md,
|
||||||
|
gap: spacing.sm,
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: colors.line,
|
||||||
|
},
|
||||||
|
pressed: {
|
||||||
|
opacity: 0.72,
|
||||||
|
},
|
||||||
|
sideRail: {
|
||||||
|
width: 2,
|
||||||
|
height: 40,
|
||||||
|
},
|
||||||
|
trackNumberWrap: {
|
||||||
|
width: 28,
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
},
|
||||||
|
trackNumber: {
|
||||||
|
...typography.label,
|
||||||
|
color: colors.textMuted,
|
||||||
|
},
|
||||||
|
dateBlock: {
|
||||||
|
width: 56,
|
||||||
|
},
|
||||||
|
day: {
|
||||||
|
color: colors.warning,
|
||||||
|
fontSize: 18,
|
||||||
|
lineHeight: 21,
|
||||||
|
fontWeight: '900',
|
||||||
|
letterSpacing: -0.6,
|
||||||
|
},
|
||||||
|
hour: {
|
||||||
|
...typography.label,
|
||||||
|
color: colors.textDim,
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
color: colors.text,
|
||||||
|
fontSize: 18,
|
||||||
|
lineHeight: 22,
|
||||||
|
fontWeight: '900',
|
||||||
|
letterSpacing: -0.6,
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
...typography.body,
|
||||||
|
color: colors.textMuted,
|
||||||
|
fontSize: 13,
|
||||||
|
lineHeight: 17,
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
|
metricLine: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: spacing.sm,
|
||||||
|
marginTop: spacing.xs,
|
||||||
|
},
|
||||||
|
metricText: {
|
||||||
|
...typography.label,
|
||||||
|
color: colors.textMuted,
|
||||||
|
},
|
||||||
|
metricTextHot: {
|
||||||
|
color: colors.action,
|
||||||
|
},
|
||||||
|
chevron: {
|
||||||
|
width: 18,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in new issue
Block a user