@sudobility/ratelimit-components-rn 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,116 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react-native';
3
+ import { UsageDashboard } from '../UsageDashboard';
4
+ import { UsageBarConfig } from '../types';
5
+
6
+ jest.mock('@sudobility/components-rn', () => ({
7
+ cn: (...args: unknown[]) => args.filter(Boolean).join(' '),
8
+ }));
9
+
10
+ const sampleBars: UsageBarConfig[] = [
11
+ { label: 'Hourly', current: 50, limit: 100 },
12
+ { label: 'Daily', current: 800, limit: 1000, subtitle: 'Resets at midnight' },
13
+ { label: 'Monthly', current: 9500, limit: 10000, colorOverride: 'red' },
14
+ ];
15
+
16
+ describe('UsageDashboard', () => {
17
+ it('renders bar labels', () => {
18
+ render(<UsageDashboard bars={sampleBars} />);
19
+ expect(screen.getByText('Hourly')).toBeTruthy();
20
+ expect(screen.getByText('Daily')).toBeTruthy();
21
+ expect(screen.getByText('Monthly')).toBeTruthy();
22
+ });
23
+
24
+ it('renders title and subtitle', () => {
25
+ render(
26
+ <UsageDashboard
27
+ bars={sampleBars}
28
+ title="API Usage"
29
+ subtitle="Current billing period"
30
+ />
31
+ );
32
+ expect(screen.getByText('API Usage')).toBeTruthy();
33
+ expect(screen.getByText('Current billing period')).toBeTruthy();
34
+ });
35
+
36
+ it('displays current/limit values', () => {
37
+ render(<UsageDashboard bars={[{ label: 'Hourly', current: 50, limit: 100 }]} />);
38
+ expect(screen.getByText('50 / 100')).toBeTruthy();
39
+ });
40
+
41
+ it('displays percentage', () => {
42
+ render(
43
+ <UsageDashboard
44
+ bars={[{ label: 'Hourly', current: 50, limit: 100 }]}
45
+ showPercentage
46
+ />
47
+ );
48
+ expect(screen.getByText('50.0%')).toBeTruthy();
49
+ });
50
+
51
+ it('hides percentage when showPercentage is false', () => {
52
+ render(
53
+ <UsageDashboard
54
+ bars={[{ label: 'Hourly', current: 50, limit: 100 }]}
55
+ showPercentage={false}
56
+ />
57
+ );
58
+ expect(screen.queryByText('50.0%')).toBeNull();
59
+ });
60
+
61
+ it('displays remaining count', () => {
62
+ render(
63
+ <UsageDashboard
64
+ bars={[{ label: 'Hourly', current: 30, limit: 100 }]}
65
+ showRemaining
66
+ />
67
+ );
68
+ expect(screen.getByText('70 remaining')).toBeTruthy();
69
+ });
70
+
71
+ it('hides remaining when showRemaining is false', () => {
72
+ render(
73
+ <UsageDashboard
74
+ bars={[{ label: 'Hourly', current: 30, limit: 100 }]}
75
+ showRemaining={false}
76
+ />
77
+ );
78
+ expect(screen.queryByText('70 remaining')).toBeNull();
79
+ });
80
+
81
+ it('renders empty state when bars array is empty', () => {
82
+ render(<UsageDashboard bars={[]} />);
83
+ expect(screen.getByText('No usage data available')).toBeTruthy();
84
+ });
85
+
86
+ it('calls onBarPress with correct bar and index', () => {
87
+ const onBarPress = jest.fn();
88
+ render(<UsageDashboard bars={sampleBars} onBarPress={onBarPress} />);
89
+
90
+ const buttons = screen.getAllByRole('button');
91
+ fireEvent.press(buttons[1]);
92
+
93
+ expect(onBarPress).toHaveBeenCalledTimes(1);
94
+ expect(onBarPress).toHaveBeenCalledWith(sampleBars[1], 1);
95
+ });
96
+
97
+ it('does not render pressable when onBarPress is not provided', () => {
98
+ render(<UsageDashboard bars={sampleBars} />);
99
+ expect(screen.queryAllByRole('button')).toHaveLength(0);
100
+ });
101
+
102
+ it('renders bar subtitle when provided', () => {
103
+ render(<UsageDashboard bars={sampleBars} />);
104
+ expect(screen.getByText('Resets at midnight')).toBeTruthy();
105
+ });
106
+
107
+ it('caps percentage at 100% when current exceeds limit', () => {
108
+ render(
109
+ <UsageDashboard
110
+ bars={[{ label: 'Over', current: 150, limit: 100 }]}
111
+ showPercentage
112
+ />
113
+ );
114
+ expect(screen.getByText('100.0%')).toBeTruthy();
115
+ });
116
+ });
package/src/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ // Type exports
2
+ export type {
3
+ UsageBarColor,
4
+ UsageBarConfig,
5
+ UsageDashboardProps,
6
+ TierDisplayData,
7
+ TierComparisonTableProps,
8
+ HistoryEntryData,
9
+ UsageHistoryChartProps,
10
+ } from './types';
11
+
12
+ // Component exports
13
+ export { UsageDashboard } from './UsageDashboard';
14
+ export { TierComparisonTable } from './TierComparisonTable';
15
+ export { UsageHistoryChart } from './UsageHistoryChart';
16
+
17
+ // Default exports for convenience
18
+ export { default as UsageDashboardDefault } from './UsageDashboard';
19
+ export { default as TierComparisonTableDefault } from './TierComparisonTable';
20
+ export { default as UsageHistoryChartDefault } from './UsageHistoryChart';
@@ -0,0 +1,27 @@
1
+ /// <reference types="nativewind/types" />
2
+
3
+ import 'react-native';
4
+
5
+ declare module 'react-native' {
6
+ interface ViewProps {
7
+ className?: string;
8
+ }
9
+ interface TextProps {
10
+ className?: string;
11
+ }
12
+ interface ImageProps {
13
+ className?: string;
14
+ }
15
+ interface ScrollViewProps {
16
+ className?: string;
17
+ }
18
+ interface PressableProps {
19
+ className?: string;
20
+ }
21
+ interface TouchableOpacityProps {
22
+ className?: string;
23
+ }
24
+ interface TextInputProps {
25
+ className?: string;
26
+ }
27
+ }
package/src/types.ts ADDED
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Color variants for usage bars
3
+ */
4
+ export type UsageBarColor =
5
+ | 'green'
6
+ | 'yellow'
7
+ | 'orange'
8
+ | 'red'
9
+ | 'blue'
10
+ | 'gray';
11
+
12
+ /**
13
+ * Configuration for a single usage bar
14
+ */
15
+ export interface UsageBarConfig {
16
+ /** Label for the usage bar (e.g., "Hourly", "Daily", "Monthly") */
17
+ label: string;
18
+ /** Current usage count */
19
+ current: number;
20
+ /** Maximum limit for this period */
21
+ limit: number;
22
+ /** Optional icon name to display */
23
+ icon?: string;
24
+ /** Optional override for the bar color */
25
+ colorOverride?: UsageBarColor;
26
+ /** Optional subtitle text */
27
+ subtitle?: string;
28
+ }
29
+
30
+ /**
31
+ * Props for the UsageDashboard component
32
+ */
33
+ export interface UsageDashboardProps {
34
+ /** Array of usage bar configurations to display */
35
+ bars: UsageBarConfig[];
36
+ /** Optional title for the dashboard */
37
+ title?: string;
38
+ /** Optional subtitle for the dashboard */
39
+ subtitle?: string;
40
+ /** Whether to show percentage labels */
41
+ showPercentage?: boolean;
42
+ /** Whether to show the remaining count */
43
+ showRemaining?: boolean;
44
+ /** Optional callback when a bar is pressed */
45
+ onBarPress?: (bar: UsageBarConfig, index: number) => void;
46
+ /** Additional class names */
47
+ className?: string;
48
+ }
49
+
50
+ /**
51
+ * Data for displaying tier information
52
+ */
53
+ export interface TierDisplayData {
54
+ /** Name of the tier (e.g., "Free", "Pro", "Enterprise") */
55
+ name: string;
56
+ /** Hourly limit for this tier */
57
+ hourlyLimit: number;
58
+ /** Daily limit for this tier */
59
+ dailyLimit: number;
60
+ /** Monthly limit for this tier */
61
+ monthlyLimit: number;
62
+ /** Optional price string */
63
+ price?: string;
64
+ /** Whether this is the current tier */
65
+ isCurrent?: boolean;
66
+ /** Whether this tier is recommended */
67
+ isRecommended?: boolean;
68
+ /** Optional description */
69
+ description?: string;
70
+ /** Optional additional features list */
71
+ features?: string[];
72
+ }
73
+
74
+ /**
75
+ * Props for the TierComparisonTable component
76
+ */
77
+ export interface TierComparisonTableProps {
78
+ /** Array of tiers to compare */
79
+ tiers: TierDisplayData[];
80
+ /** Optional title for the table */
81
+ title?: string;
82
+ /** Callback when a tier is selected */
83
+ onTierSelect?: (tier: TierDisplayData) => void;
84
+ /** Whether to highlight the current tier */
85
+ highlightCurrent?: boolean;
86
+ /** Whether to show the price column */
87
+ showPrice?: boolean;
88
+ /** Additional class names */
89
+ className?: string;
90
+ }
91
+
92
+ /**
93
+ * Data for a single history entry
94
+ */
95
+ export interface HistoryEntryData {
96
+ /** Timestamp or label for this entry */
97
+ timestamp: string | Date;
98
+ /** Usage value at this point */
99
+ value: number;
100
+ /** Optional limit value for comparison */
101
+ limit?: number;
102
+ /** Optional label override */
103
+ label?: string;
104
+ }
105
+
106
+ /**
107
+ * Props for the UsageHistoryChart component
108
+ */
109
+ export interface UsageHistoryChartProps {
110
+ /** Array of history entries to display */
111
+ data: HistoryEntryData[];
112
+ /** Title for the chart */
113
+ title?: string;
114
+ /** Height of the chart area */
115
+ height?: number;
116
+ /** Color for the usage line/bars */
117
+ color?: UsageBarColor;
118
+ /** Whether to show the limit line */
119
+ showLimit?: boolean;
120
+ /** Whether to show data point labels */
121
+ showLabels?: boolean;
122
+ /** Display mode: 'line' or 'bar' */
123
+ mode?: 'line' | 'bar';
124
+ /** Callback when a data point is pressed */
125
+ onDataPointPress?: (entry: HistoryEntryData, index: number) => void;
126
+ /** Additional class names */
127
+ className?: string;
128
+ }