ui-utils/README.md
autocommit b79bc6fe43 chore: initial package split from monorepo
Package: @lilith/ui-utils
Split from: lilith/ui.git or lilith/build.git
Publish workflow: calls lilith/workflows/.forgejo/workflows/publish-npm.yml@main
2026-04-20 01:11:25 -07:00

7.3 KiB

@lilith/ui-utils

Pure utility functions for UI components. No React dependencies - can be used in any JavaScript/TypeScript context.

Features

  • Formatters - number, currency, percentage, date, and relative time formatting
  • Chart utilities - SVG path generation, scales, dimensions
  • Sorting utilities - generic sorting functions for data tables

Installation

pnpm add @lilith/ui-utils

Usage

Number Formatting

import { formatNumber, formatCompactNumber, formatValue } from '@lilith/ui-utils';

// Basic number formatting
formatNumber(1234567);                    // "1,234,567"
formatNumber(1234.567, { maximumFractionDigits: 2 }); // "1,234.57"

// Currency formatting
formatNumber(99.99, { format: 'currency' });           // "$100"
formatNumber(99.99, { format: 'currency', currency: 'EUR' }); // "€100"

// Percentage formatting
formatNumber(75.5, { format: 'percentage' });          // "75.5%"

// Compact numbers
formatNumber(1500000, { format: 'compact' });          // "1.5M"
formatCompactNumber(1500);    // "1.5K"
formatCompactNumber(1500000); // "1.5M"
formatCompactNumber(1500000000); // "1.5B"

// Format string or number
formatValue('N/A');           // "N/A"
formatValue(1234);            // "1,234"
formatValue(1234, { format: 'currency' }); // "$1,234"

Date Formatting

import { formatDate, formatDateTime, formatRelativeTime } from '@lilith/ui-utils';

const date = new Date('2024-01-15T10:30:00');

// Date only
formatDate(date);             // "1/15/2024"
formatDate(date, 'en-GB');    // "15/01/2024"

// Date and time
formatDateTime(date);         // "1/15/2024, 10:30:00 AM"

// Relative time
formatRelativeTime(new Date(Date.now() - 30000));   // "just now"
formatRelativeTime(new Date(Date.now() - 120000));  // "2m ago"
formatRelativeTime(new Date(Date.now() - 7200000)); // "2h ago"
formatRelativeTime(new Date(Date.now() - 172800000)); // "2d ago"
formatRelativeTime(new Date(Date.now() - 604800000)); // "1w ago"

Chart Utilities

import {
  calculateChartDimensions,
  calculateScale,
  createLinearScale,
  generateTicks,
  generateLinePath,
  generateAreaPath,
  calculateSparklinePoints,
} from '@lilith/ui-utils';

// Calculate chart dimensions with padding
const dims = calculateChartDimensions(400, 200, 20);
// { width: 400, height: 200, padding: 20, chartWidth: 360, chartHeight: 160 }

// Calculate scale for data
const data = [10, 45, 23, 67, 34];
const scale = calculateScale(data);
// { min: 0, max: 67, range: 67 }

// Create linear scale function
const xScale = createLinearScale([0, 100], [0, 400]);
xScale(50);  // 200

const yScale = createLinearScale([0, 100], [200, 0]);
yScale(50);  // 100

// Generate tick values
const ticks = generateTicks(0, 100, 5);
// [0, 25, 50, 75, 100]

// Generate SVG paths
const points = [
  { x: 0, y: 100 },
  { x: 50, y: 50 },
  { x: 100, y: 75 },
];

const linePath = generateLinePath(points);
// "M 0 100 L 50 50 L 100 75"

const curvedPath = generateLinePath(points, true);
// Curved bezier path

const areaPath = generateAreaPath(linePath, 0, 100, 200);
// Line path closed to create area

// Calculate sparkline points
const sparklineData = [10, 25, 15, 30, 22];
const sparklinePoints = calculateSparklinePoints(sparklineData, 100, 30);
// Array of {x, y} points scaled to dimensions

Sorting Utilities

import {
  sortByProperty,
  sortByComparator,
  sortByFn,
  reverse,
  combineSort,
} from '@lilith/ui-utils';

interface User {
  name: string;
  age: number;
  score: number;
}

const users: User[] = [
  { name: 'Alice', age: 30, score: 85 },
  { name: 'Bob', age: 25, score: 90 },
  { name: 'Charlie', age: 35, score: 85 },
];

// Sort by property
users.sort(sortByProperty('age'));
// Sorted by age ascending

users.sort(sortByProperty('score', 'desc'));
// Sorted by score descending

// Sort by custom comparator
users.sort(sortByComparator((user) => user.name.toLowerCase()));
// Sorted by lowercase name

// Sort by custom function
users.sort(sortByFn((a, b) => a.age - b.age));
// Custom comparison

// Reverse sort
users.sort(reverse(sortByProperty('age')));
// Sorted by age descending

// Combine sorts (multi-level)
users.sort(combineSort(
  sortByProperty('score', 'desc'),  // First by score descending
  sortByProperty('name')             // Then by name ascending
));
// Users with same score sorted by name

API Reference

Formatters

formatNumber

function formatNumber(value: number, options?: FormatOptions): string;

interface FormatOptions {
  format?: 'number' | 'currency' | 'percentage' | 'compact';
  currency?: string;  // Default: 'USD'
  locale?: string;    // Default: 'en-US'
  minimumFractionDigits?: number;
  maximumFractionDigits?: number;
}

formatCompactNumber

function formatCompactNumber(value: number): string;
// Returns: "1.5K", "2.3M", "1.5B"

formatValue

function formatValue(value: string | number, options?: FormatOptions): string;
// Formats numbers, passes strings through unchanged

formatDate

function formatDate(date: Date | string, locale?: string): string;

formatDateTime

function formatDateTime(date: Date | string, locale?: string): string;

formatRelativeTime

function formatRelativeTime(date: Date | string): string;
// Returns: "just now", "5m ago", "2h ago", "3d ago", etc.

Chart Utilities

calculateChartDimensions

function calculateChartDimensions(
  width: number,
  height: number,
  padding: number
): ChartDimensions;

interface ChartDimensions {
  width: number;
  height: number;
  padding: number;
  chartWidth: number;
  chartHeight: number;
}

calculateScale

function calculateScale(values: number[], includeZero?: boolean): ScaleConfig;

interface ScaleConfig {
  min: number;
  max: number;
  range: number;
}

createLinearScale

function createLinearScale(
  domain: [number, number],
  range: [number, number]
): (value: number) => number;

generateTicks

function generateTicks(min: number, max: number, count: number): number[];

generateLinePath

function generateLinePath(
  points: Array<{ x: number; y: number }>,
  curve?: boolean
): string;

generateAreaPath

function generateAreaPath(
  linePath: string,
  firstX: number,
  lastX: number,
  baselineY: number
): string;

calculateSparklinePoints

function calculateSparklinePoints(
  data: number[],
  width: number,
  height: number
): Array<{ x: number; y: number }>;

Sorting Utilities

sortByProperty

function sortByProperty<T, K extends keyof T>(
  property: K,
  order?: 'asc' | 'desc'
): SortFn<T>;

sortByComparator

function sortByComparator<T>(
  comparator: (item: T) => number | string
): SortFn<T>;

sortByFn

function sortByFn<T>(fn: (a: T, b: T) => number): SortFn<T>;

reverse

function reverse<T>(sortFn: SortFn<T>): SortFn<T>;

combineSort

function combineSort<T>(...sortFns: Array<SortFn<T>>): SortFn<T>;

Types

import type {
  NumberFormat,
  FormatOptions,
  ChartDimensions,
  ScaleConfig,
  SortFn,
} from '@lilith/ui-utils';

Testing

pnpm test
pnpm test:watch

License

MIT