From 8232882977bd041853c2a2109fe8c0d6ef6726ab Mon Sep 17 00:00:00 2001 From: fightrate-bot Date: Tue, 16 Jun 2026 01:48:12 -0300 Subject: [PATCH] feat(components): DialTile for sensor signal rows --- src/__tests__/DialTile.test.tsx | 34 +++++++++++++++++ src/components/DialTile.tsx | 65 +++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 src/__tests__/DialTile.test.tsx create mode 100644 src/components/DialTile.tsx diff --git a/src/__tests__/DialTile.test.tsx b/src/__tests__/DialTile.test.tsx new file mode 100644 index 0000000..735b3bf --- /dev/null +++ b/src/__tests__/DialTile.test.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import TestRenderer from 'react-test-renderer'; + +import { DialTile } from '../components/DialTile'; + +describe('DialTile', () => { + it('renders label and value', () => { + let tree: any; + TestRenderer.act(() => { + tree = TestRenderer.create(React.createElement(DialTile, { label: 'RSSI', value: '-61 dBm', testID: 'dial' })); + }); + const json = JSON.stringify(tree.toJSON()); + expect(json).toContain('RSSI'); + expect(json).toContain('-61 dBm'); + }); + + it('combines label and value into accessibilityLabel', () => { + let tree: any; + TestRenderer.act(() => { + tree = TestRenderer.create(React.createElement(DialTile, { label: 'RSSI', value: '-61 dBm', testID: 'dial' })); + }); + const json = JSON.stringify(tree.toJSON()); + expect(json).toContain('"accessibilityLabel":"RSSI: -61 dBm"'); + }); + + it('uses action color when tone=red', () => { + let tree: any; + TestRenderer.act(() => { + tree = TestRenderer.create(React.createElement(DialTile, { label: 'X', value: 'Y', tone: 'red', testID: 'dial' })); + }); + const json = JSON.stringify(tree.toJSON()); + expect(json).toContain('#E23B2E'); + }); +}); diff --git a/src/components/DialTile.tsx b/src/components/DialTile.tsx new file mode 100644 index 0000000..7c076b4 --- /dev/null +++ b/src/components/DialTile.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { StyleSheet, Text, View, type ViewStyle } from 'react-native'; +import { colors, spacing, typography } from '../theme'; + +type Tone = 'amber' | 'green' | 'red'; + +type Props = { + readonly label: string; + readonly value: string; + readonly tone?: Tone; + readonly testID?: string; +}; + +function toneColor(tone: Tone | undefined): string { + if (tone === 'red') return colors.action; + if (tone === 'green') return colors.success; + return colors.warning; +} + +export function DialTile({ label, value, tone = 'amber', testID }: Props) { + const markerStyle: ViewStyle = { backgroundColor: toneColor(tone) }; + return ( + + + + + {value} + {label} + + ); +} + +const styles = StyleSheet.create({ + row: { + minHeight: 56, + flexDirection: 'row', + alignItems: 'center', + borderTopWidth: 1, + borderColor: colors.line, + paddingVertical: spacing.sm, + paddingHorizontal: spacing.md, + gap: spacing.md, + }, + markerWrap: { + width: 4, + height: 32, + justifyContent: 'center', + }, + marker: { + width: 2, + height: 32, + }, + value: { + ...typography.detailNumeric, + flex: 1, + }, + label: { + ...typography.label, + color: colors.textDim, + }, +});