Skip to content

Utilities ​

Fifty-eight helpers across nine groups. Enough that an admin screen never has to pull in lodash, and small enough to read.

Two rules run through all of them. Nothing mutates — sorting a table must not reorder the store behind it. And the awkward case is handled, because the reason a helper exists is usually the edge that a < b or .slice() gets wrong.

The groups ​

GroupFunctionsWhat it covers
Array8Grouping, indexing, deduplicating, locale-aware sorting, chunking, totals, reordering.
String7Case, slugs, truncation, initials, masking — all grapheme-aware.
Number7Clamping, correct rounding, byte sizes, percentages, numeric input guards.
Object7Pick, omit, safe nested reads, cleaning a query object, a real emptiness test.
File4Downloading a blob, reading a file, the extension.
Color6Hex and RGB, contrast ratio, a readable foreground, a stable color from a string.
Intl4The locale tag, graphemes, and the shared collator.
Format7Dates, numbers, currency and compact figures, on the active locale.
Time columns8The hour and minute columns behind the time picker.

Import paths ​

Each group is its own module under @shared/utils:

ts
import { chunk, groupBy, sortBy } from '@shared/utils/array'
import { formatCurrency, formatDate } from '@shared/utils/format'
import { contrastColor } from '@shared/utils/color'

Five more live in four modules of their own and are documented with the group they belong to: deepEqual, deepMerge, stripHtml, and the pair in numericInput.

Locale-aware by default ​

Anything that compares or renders text takes the app's active locale unless you pass one:

  • sortBy goes through Intl.Collator, never a < b, which would put Zurich before Ávila.
  • capitalize and initials respect Turkish dotted I, so ismail becomes İsmail in tr and Ismail in en.
  • truncate, initials and maskString count graphemes, not UTF-16 units, so a ZWJ family emoji is one character rather than eleven.
  • formatBytes and everything in Format render digits through Intl, so a German dialog reads 1,5 MB.

Guards, not exceptions ​

Most of these run inside a computed, where throwing takes the page down. So they return a sensible value instead:

CaseWhat happens
clamp(v, min, max) with min > maxReturns min.
chunk(items, 0)Size is coerced to at least 1, rather than looping forever.
percentage(n, 0)Returns 0. A NaN CSS width silently draws a full bar.
sumBy over a row that yields NaNThat row is skipped, so one bad value cannot poison a total.
move(items, from, to) out of rangeReturns the list unchanged, rather than dropping or duplicating.
get(obj, 'a.b.c') through a missing linkReturns undefined, or your fallback.

Try them ​

Every group page carries a live playground, running the template's own helpers. Change an input and the result follows.

Where to go next ​