Create source implementations for packages imported by NomGap: - @bytelyst/accessibility — ARIA helper functions (alertLabel, progressLabel, etc.) - @bytelyst/celebrations — celebration engine for milestones - @bytelyst/gentle-notifications — guilt-free notification filtering - @bytelyst/time-references — human-friendly fasting time references - @bytelyst/subscription-client — billing/subscription HTTP client - @bytelyst/quick-actions — progressive disclosure UI helpers - @bytelyst/referral-client — referral program client - @bytelyst/marketplace-client — influencer marketplace client - @bytelyst/org-client — B2B org management client Made-with: Cursor
125 lines
2.8 KiB
TypeScript
125 lines
2.8 KiB
TypeScript
export type AlertA11yProps = {
|
|
role: 'alert';
|
|
'aria-live': 'assertive' | 'polite';
|
|
'aria-label': string;
|
|
};
|
|
|
|
export function alertLabel(level: string, description: string): AlertA11yProps {
|
|
return {
|
|
role: 'alert',
|
|
'aria-live': level === 'danger' ? 'assertive' : 'polite',
|
|
'aria-label': description,
|
|
};
|
|
}
|
|
|
|
export type ProgressbarA11yProps = {
|
|
role: 'progressbar';
|
|
'aria-label': string;
|
|
'aria-valuenow': number;
|
|
'aria-valuemin': number;
|
|
'aria-valuemax': number;
|
|
'aria-valuetext': string;
|
|
};
|
|
|
|
export function progressLabel(
|
|
label: string,
|
|
valuePct: number,
|
|
description: string,
|
|
): ProgressbarA11yProps {
|
|
return {
|
|
role: 'progressbar',
|
|
'aria-label': label,
|
|
'aria-valuenow': valuePct,
|
|
'aria-valuemin': 0,
|
|
'aria-valuemax': 100,
|
|
'aria-valuetext': description,
|
|
};
|
|
}
|
|
|
|
export type AriaLabelOnly = { 'aria-label': string };
|
|
|
|
export function streakLabel(days: number): AriaLabelOnly {
|
|
return { 'aria-label': `${days} day streak` };
|
|
}
|
|
|
|
export type ButtonA11yProps = {
|
|
'aria-label': string;
|
|
'aria-roledescription'?: string;
|
|
};
|
|
|
|
export function buttonLabel(text: string, hint?: string): ButtonA11yProps {
|
|
return {
|
|
'aria-label': text,
|
|
...(hint ? { 'aria-roledescription': hint } : {}),
|
|
};
|
|
}
|
|
|
|
export function achievementLabel(name: string, description: string): AriaLabelOnly {
|
|
return { 'aria-label': `Achievement: ${name} — ${description}` };
|
|
}
|
|
|
|
export type TimerA11yProps = {
|
|
'aria-label': string;
|
|
'aria-live': 'polite';
|
|
};
|
|
|
|
export function timerLabel(
|
|
hours: number,
|
|
minutes: number,
|
|
seconds: number,
|
|
status: string,
|
|
): TimerA11yProps {
|
|
return {
|
|
'aria-label': `Timer: ${hours}h ${minutes}m ${seconds}s, ${status}`,
|
|
'aria-live': 'polite',
|
|
};
|
|
}
|
|
|
|
export type SliderA11yProps = {
|
|
role: 'slider';
|
|
'aria-label': string;
|
|
'aria-valuenow': number;
|
|
'aria-valuemin': number;
|
|
'aria-valuemax': number;
|
|
};
|
|
|
|
export function sliderLabel(
|
|
label: string,
|
|
value: number,
|
|
min: number,
|
|
max: number,
|
|
): SliderA11yProps {
|
|
return {
|
|
role: 'slider',
|
|
'aria-label': label,
|
|
'aria-valuenow': value,
|
|
'aria-valuemin': min,
|
|
'aria-valuemax': max,
|
|
};
|
|
}
|
|
|
|
function plural(n: number, singular: string, pluralForm: string): string {
|
|
const word = n === 1 ? singular : pluralForm;
|
|
return `${n} ${word}`;
|
|
}
|
|
|
|
/**
|
|
* Spoken-friendly duration from a fractional hour value, e.g. 12 → "12 hours", 1.5 → "1 hour 30 minutes".
|
|
*/
|
|
export function formatDurationForA11y(hours: number): string {
|
|
const totalMinutes = Math.round(hours * 60);
|
|
const h = Math.floor(totalMinutes / 60);
|
|
const m = totalMinutes % 60;
|
|
|
|
if (h === 0 && m === 0) {
|
|
return '0 minutes';
|
|
}
|
|
if (m === 0) {
|
|
return plural(h, 'hour', 'hours');
|
|
}
|
|
if (h === 0) {
|
|
return plural(m, 'minute', 'minutes');
|
|
}
|
|
return `${plural(h, 'hour', 'hours')} ${plural(m, 'minute', 'minutes')}`;
|
|
}
|