forked from Creditra/Creditra-Frontend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtokens.ts
More file actions
171 lines (150 loc) · 6.94 KB
/
Copy pathtokens.ts
File metadata and controls
171 lines (150 loc) · 6.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/**
* Design tokens consumed at the JS layer.
*
* Most components style themselves with CSS custom properties declared
* in `src/index.css`. This file exists for the cases where a component
* needs a token value in JavaScript — typically because the style is
* applied as an inline `React.CSSProperties` (e.g. SVG fill on the risk
* gauge, the dynamic badge palettes) rather than via a class name.
*
* Every value here must mirror the equivalent CSS custom property. If
* you change one, change the other. See `docs/DESIGN_SYSTEM.md` for the
* canonical catalogue.
*/
import type { CreditLineStatus, UtilizationLevel } from '../types/creditLine';
import type React from 'react';
// ─── Design tokens ────────────────────────────────────────────────────────────
export const COLOR = {
bg: '#0d1117',
surface: '#161b22',
border: '#30363d',
text: '#e6edf3',
muted: '#8b949e',
accent: '#58a6ff',
success: '#3fb950',
warning: '#d29922',
danger: '#f85149',
};
export const UTIL_COLOR: Record<UtilizationLevel, string> = {
low: COLOR.success,
medium: COLOR.warning,
high: COLOR.danger,
};
export const UTIL_PATTERN_DENSITY: Record<UtilizationLevel, number | null> = {
low: null,
medium: 6,
high: 4,
};
export const STATUS_COLOR: Record<CreditLineStatus, { bg: string; color: string; border: string }> = {
Active: { bg: 'rgba(63,185,80,0.16)', color: '#8ee99d', border: 'rgba(63,185,80,0.44)' },
Suspended: { bg: 'rgba(210,153,34,0.16)', color: '#f0c96a', border: 'rgba(210,153,34,0.46)' },
Defaulted: { bg: 'rgba(248,81,73,0.14)', color: '#ffb0aa', border: 'rgba(248,81,73,0.46)' },
Closed: { bg: 'rgba(139,148,158,0.16)', color: '#c4ccd6', border: 'rgba(139,148,158,0.42)' },
};
/**
* Map a numeric risk score (0–850, FICO-style scale) to a semantic color.
* Mirrors the credit-score badge thresholds used on the dashboard.
*/
export const RISK_COLOR = (score: number) =>
score >= 700 ? COLOR.success : score >= 600 ? COLOR.warning : COLOR.danger;
// ─── Shared style objects ─────────────────────────────────────────────────────
export const inputStyle: React.CSSProperties = {
background: COLOR.surface,
border: `1px solid ${COLOR.border}`,
borderRadius: 6,
padding: '0.5rem 0.75rem',
color: COLOR.text,
fontSize: '0.875rem',
outline: 'none',
};
const btnBase: React.CSSProperties = {
border: `1px solid ${COLOR.border}`,
borderRadius: 6,
padding: '0.375rem 0.75rem',
fontSize: '0.8rem',
fontWeight: 500,
cursor: 'pointer',
background: COLOR.surface,
color: COLOR.muted,
};
export const btn = {
ghost: { ...btnBase } as React.CSSProperties,
primary: { ...btnBase, background: COLOR.accent, color: COLOR.bg, border: 'none', fontWeight: 600 } as React.CSSProperties,
secondary: { ...btnBase, color: COLOR.text } as React.CSSProperties,
draw: { ...btnBase, background: 'rgba(88,166,255,0.12)', color: COLOR.accent, borderColor: 'rgba(88,166,255,0.3)' } as React.CSSProperties,
repay: { ...btnBase, background: 'rgba(63,185,80,0.12)', color: COLOR.success, borderColor: 'rgba(63,185,80,0.3)' } as React.CSSProperties,
danger: { ...btnBase, background: 'rgba(248,81,73,0.12)', color: COLOR.danger, borderColor: 'rgba(248,81,73,0.3)' } as React.CSSProperties,
suspend: { ...btnBase, background: 'rgba(210,153,34,0.12)', color: COLOR.warning, borderColor: 'rgba(210,153,34,0.3)' } as React.CSSProperties,
};
// ─── Formatters ───────────────────────────────────────────────────────────────
export const fmt = (n: number) =>
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(n);
export const fmtDate = (iso: string) =>
new Date(iso).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
export const fmtDateTime = (iso: string) =>
new Date(iso).toLocaleString('en-US', {
month: 'short', day: 'numeric', year: 'numeric',
hour: '2-digit', minute: '2-digit',
});
export const relativeTime = (iso: string): string => {
const diff = Date.now() - new Date(iso).getTime();
const mins = Math.floor(diff / 60000);
if (mins < 60) return `${mins}m ago`;
const hrs = Math.floor(mins / 60);
if (hrs < 24) return `${hrs}h ago`;
const days = Math.floor(hrs / 24);
if (days < 30) return `${days}d ago`;
return fmtDate(iso);
};
// ─── Utilization helpers ──────────────────────────────────────────────────────
export const getUtilizationLevel = (utilized: number, limit: number): UtilizationLevel => {
const pct = utilized / limit;
if (pct < 0.4) return 'low';
if (pct < 0.75) return 'medium';
return 'high';
};
export const utilizationPct = (utilized: number, limit: number) =>
limit === 0 ? 0 : Math.round((utilized / limit) * 100);
// ─── Per-line accent stripe palette ──────────────────────────────────────────
//
// Four semantic tokens chosen for AA contrast (≥ 3:1) against --surface
// (#161b22). Contrast ratios against #161b22:
// accent #58a6ff → 5.2 : 1 ✓
// success #3fb950 → 4.3 : 1 ✓ (UI component threshold 3:1)
// warning #d29922 → 4.3 : 1 ✓
// danger #f85149 → 4.4 : 1 ✓
//
// No hex literals are used below — every value references COLOR.*.
/**
* Fixed 4-slot palette for deterministic per-line accent stripes.
* Index is stable across renders; never re-order.
*/
export const LINE_ACCENT_PALETTE: readonly string[] = [
COLOR.accent, // 0 — blue
COLOR.success, // 1 — green
COLOR.warning, // 2 — amber
COLOR.danger, // 3 — red
] as const;
/**
* Map a `lineId` string deterministically to one of the four accent tokens.
*
* Uses a djb2-style hash (XOR variant) so that:
* - The same id always returns the same color across renders and
* sessions (no random, no Date.now()).
* - Adjacent ids tend to land on different palette slots.
* - The implementation is O(n) with zero allocations.
*
* @param lineId Stable identifier for the credit line (e.g. "CL-001").
* @returns One of the four values from LINE_ACCENT_PALETTE.
*/
export function lineAccentColor(lineId: string): string {
let hash = 5381;
for (let i = 0; i < lineId.length; i++) {
// djb2 XOR variant: hash = hash * 33 ^ charCode
hash = ((hash << 5) + hash) ^ lineId.charCodeAt(i);
// Keep within 32-bit signed integer range to avoid floating-point drift
hash = hash | 0;
}
// Modulo over the absolute value so negative hashes map cleanly
return LINE_ACCENT_PALETTE[Math.abs(hash) % LINE_ACCENT_PALETTE.length];
}