@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.
- package/dist/TierComparisonTable.d.ts +7 -0
- package/dist/TierComparisonTable.d.ts.map +1 -0
- package/dist/UsageDashboard.d.ts +7 -0
- package/dist/UsageDashboard.d.ts.map +1 -0
- package/dist/UsageHistoryChart.d.ts +7 -0
- package/dist/UsageHistoryChart.d.ts.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1423 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1423 -0
- package/dist/index.mjs.map +1 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +64 -0
- package/src/TierComparisonTable.tsx +220 -0
- package/src/UsageDashboard.tsx +201 -0
- package/src/UsageHistoryChart.tsx +365 -0
- package/src/__tests__/TierComparisonTable.test.tsx +143 -0
- package/src/__tests__/UsageDashboard.test.tsx +116 -0
- package/src/index.ts +20 -0
- package/src/nativewind.d.ts +27 -0
- package/src/types.ts +128 -0
|
@@ -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
|
+
}
|