Skip to content

MikeSpa/kit

Repository files navigation

@mikespa/kit

CI npm version codecov minzipped size

A small, framework-agnostic toolkit. Built to stop copy-pasting the same formatCHF / slugify / clamp functions between Next.js projects.

No runtime dependencies. Tree-shakeable via subpath exports.

Install

npm install @mikespa/kit

Usage

Import only what you need via subpaths — this keeps bundles small and is self-documenting at the call site.

import { formatDate, formatCHF, formatSecs, formatHours } from '@mikespa/kit/format';
import { slugify, truncate } from '@mikespa/kit/string';
import { clamp, timeDiffHours } from '@mikespa/kit/math';

formatDate('2026-06-19');              // "19.06.2026"
formatCHF(1234.5);                     // "1'234.50 CHF"
formatSecs(9000);                      // "2h30"
formatHours(2.5);                      // "2h30"
slugify('Hello, World!');              // "hello-world"
clamp(15, 0, 10);                      // 10
timeDiffHours('08:00', '10:30');       // 2.5

Or import everything from the root if you don't care about tree-shaking:

import { formatCHF, slugify, clamp } from '@mikespa/kit';

Modules

./format

Function Description Default output
formatDate(input) Date as DD.MM.YYYY "19.06.2026"
formatTime(input) Time (24h) "15:30"
formatDateTime(input) Date + time (locale-formatted) "19 juin 2026, 15:30"
formatDateRange(start, end) Compact date range (locale-formatted) "01.06.2026 – 15.06.2026"
formatRelative(input) Relative to now (locale-formatted) "il y a 3 heures"
isToday(input) Whether date is today true
isPast(input) Whether date is in the past true
isFuture(input) Whether date is in the future false
isDateInRange(input, start, end) Whether date falls within [start, end] true
startOfDay(input) Midnight of the given date Date
endOfDay(input) 23:59:59.999 of the given date Date
parseSwissDate(input) Parse "DD.MM.YYYY" → ISO string "2026-06-19"
formatNumber(value) Locale-aware thousands "1 234 567"
formatCHF(amount) Swiss CHF, apostrophe sep. "1'234.50 CHF"
formatCurrency(value) Intl currency (CHF default) "1 234.50 CHF"
formatCompact(value) Compact notation "1,2 k"
parseCHF(input) Parse Swiss amount → number 1234.5
formatPercent(value) Percentage "45,6%"
formatBytes(bytes) Human-readable file size "1.5 KB"
formatPhone(input) Swiss phone number "+41 79 123 45 67"
isValidPhone(input) Validate Swiss phone number shape true
maskPhone(input) Mask phone number for safe display "+41 79 *** ** 67"
formatIBAN(input) IBAN with spaces "CH56 0483 5012 3456 7800 9"
isValidIBAN(input) Validate IBAN check digits (any country) true
maskIBAN(input) Mask IBAN for safe display "CH56 **** **** **** **** 9"
formatAHV(input) AHV/OASI number, grouped "756.9217.0769.85"
isValidAHV(input) Validate AHV/OASI check digit true
maskAHV(input) Mask AHV number for safe display "756...85"
formatQRReference(input) QR-bill/ISR reference, grouped "21 00000 00003 13947 14300 09017"
isValidQRReference(input) Validate QR-bill/ISR check digit true
formatUID(input) UID/VAT number, grouped "CHE-116.281.710"
isValidUID(input) Validate UID/VAT check digit true
isValidSwissPostalCode(input) Validate 4-digit postal code range true
formatSecs(seconds, step?) Duration from seconds "2h30" / "45min"
formatMinutes(minutes, step?) Duration from minutes "2h30" / "45min"
formatHours(hours, step?) Duration from decimal hours "2h30" / "45min"
formatHoursDecimal(hours) Decimal duration from hours "2.50h"
formatMinutesDecimal(minutes) Decimal duration from minutes "45.00min"
formatSecsDecimal(seconds) Decimal duration from seconds "90.00s"
parseDurationInput(input) Parse "2h30" / "2.5" → minutes 150

All format* functions that wrap Intl accept an optional locale parameter (defaults to 'fr-CH') so consuming projects can override per-call.

For apps with a variable/per-request locale (e.g. a language switcher, or SSR with per-request locale), createFormatters(locale) returns those same functions pre-bound to one locale, so call sites don't repeat { locale } everywhere. It's a plain factory — no shared/global state, safe under concurrent requests:

import { createFormatters } from '@mikespa/kit/format';

const fmt = createFormatters('de-CH');
fmt.formatDate(order.date);
fmt.formatCurrency(order.total);

createFormatters binds: formatDate, formatTime, formatDateTime, formatRelative, formatNumber, formatCompact, formatCurrency, formatPercent.

./string

Function Description
slugify(input) URL-friendly slug
truncate(input, maxLength) Truncate with suffix
capitalize(input) First letter uppercase
pluralize(count, singular) Simple pluralizer

./math

Function Description
clamp(value, min, max) Clamp to range
lerp(start, end, t) Linear interpolate
round(value, decimals) Round to N decimals
timeDiffHours(from, to, overnight?) Hours between "HH:MM" strings

./array

Function Description
groupBy(arr, keyFn) Group items into a Record by key
uniqueBy(arr, keyFn) Deduplicate by derived key
sortBy(arr, keyFn, dir?) Sort by derived key, asc or desc

./object

Function Description
pick(obj, keys) New object with only specified keys
omit(obj, keys) New object with specified keys removed

Development

npm install
npm run dev          # build in watch mode
npm run build        # build once (output -> dist/)
npm test             # run tests
npm run test:watch
npm run typecheck

License

MIT

About

A small, framework-agnostic toolkit. Built to stop copy-pasting the same formatCHF / slugify / clamp functions between Next.js projects. No runtime dependencies. Tree-shakeable via subpath exports.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors