npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@reykjavik/hanna-utils

v0.2.17

Published

A collection of vanilla JavaScript functions and constants, that tend to be helpful when working with (or within) the Hanna design system.

Downloads

662

Readme

@reykjavik/hanna-utils

A collection of vanilla JavaScript functions and constants, that tend to be helpful when working with (or within) the Hanna design system.

This library is also a core dependency of most other packages in this repo, including hanna-react, hanna-css, and more.

yarn add @reykjavik/hanna-utils

Table of Contents:

Misc Utilities

getSVGtext

Syntax: getSVGtext(url: string | undefined, altText?: string): Promise<string>

Fetches a remote SVG file and returns its markup contents — excluding any leading <?xml /> directives or "Generator" comments.

If you pass the optional altText parameter, it will attempt to inject a <title/> element into the SVG string. (First removing existing <title/>.)

import { getSVGtext } from '@reykjavik/hanna-utils';

const svgUrl = 'https://styles.reykjavik.is/assets/reykjavik-logo.svg';

getSVGtext(svgUrl).then((svgMarkup) => {
  document.body.insertAdjacentHTML('beforeend', svgMarkup);
});

To check if file is svg:

import { getSVGtext } from '@reykjavik/hanna-utils';

const isSVG: true = getSVGtext.isSvgUrl(
  'https://styles.reykjavik.is/assets/reykjavik-logo.svg'
);

getFormatMonitor

Returns an object that contains info about the currently active screen media format and a way to subscribe/unsubscribe callbacks to whenever the window switches over to another "media-format"

The Hanna CSS module/token -basics configures certain media-query breakpoints with human-friendly names (i.e. "phone", "phablet", "tablet", "netbook", "wide")

NOTE: In server-side environments (without window and document objects) the exported "formatMonitor" object will not "start" and remains completely inactive.

import { getFormatMonitor } from '@reykjavik/hanna-utils';
import type { MediaFormat } from '@reykjavik/hanna-utils';

formatMonitor = getFormatMonitor();

formatMonitor.media.is; // e.g. 'wide';

formatMonitor.subscribe((media: MediaFormat) => {
  media === formatMonitor.media; // true;
  // Do something because `media.is` has changed,
});

See https://github.com/maranomynet/formatchange#3-getting-the-current-media-format for more info.

(This utility is, for example, utilized by the @reykjavik/hanna-react package to implement its useFormatMonitor hook.)

printDate

Syntax: printDate(date: string | Date, lang?: string): string

Very simple, very stupid, standalone date formatter for Icelandic (default), English and Polish.

Just prints the full date (day, month, year). No bells. No whistles. No options.

import { printDate } from '@reykjavik/hanna-utils';

printDate('2022-04-30', 'is'); // 30. apríl 2022
printDate(new Date('2022-04-30'), 'en'); // April 30, 2022
printDate('2022-04-30', 'pl'); // 30. kwietnia 2022

getStableRandomItem

Syntax: getStableRandomItem<T>(items: ReadonlyArray<T> | Record<string, T>, seed: string): T

Returns a pseudo random item from a collection of items (array or record), based on a given seed key/id. The function always returns the same item for a given collection and seed.

You might want to use this helper inside React components to get a stable randomness, without having to resort to hooks.

import { getStableRandomItem } from '@reykjavik/hanna-utils';
import { blingTypes } from '@reykjavik/hanna-utils/assets';

// ...inside a component

const randomBling = getStableRandomItem(blingTypes, props.newsHeadline);

capitalize

Syntax: capitalize<Str extends string>(str: Str, locale?: string): Capitalize<Str>

Simple 'foo bar' --> 'Foo bar' mapper.

Default locale: "IS" (effectively same as "EN" and vanilla toUpperCase())

import { capitalize } from '@reykjavik/hanna-utils';

capitalize('hello world'); // "Hello world"
capitalize('istanbul', 'TR'); // "İstanbul"

classes

Syntax: classes(...args: Array<string | Falsy | Array<string | Falsy>>): string

Filters and joins a messy list of CSS classNames, neatly skipping falsy values.

import { classes } from '@reykjavik/hanna-utils';

const className = classes(
  'A',
  false,
  '',
  'B',
  ['C', null, [undefined, 'D']],
  null
);

console.log(className);
// 'A B C D'

modifiedClass

Syntax: modifiedClass(base: string, modifiers: string | falsy | Array<string | Falsy>, extraClass?: string): string

Constructs a BEM class-name with one or more optional "--modifier" flags.

import { modifiedClass } from '@reykjavik/hanna-utils';

const className = modifiedClass('MyComponent', 'primary', 'extra-class');
// 'MyComponent MyComponent--primary extra-class'

const className2 = modifiedClass('MyComponent', ['primary', 'large']);
// 'MyComponent MyComponent--primary MyComponent--large'

const className3 = modifiedClass('MyComponent', null);
// 'MyComponent'

const className4 = modifiedClass('MyComponent', [false, '', 'error']);
// 'MyComponent MyComponent--error'

Asset helpers

Reykjavík Logo

Syntax: getRvkLogoUrl(logoFile: RvkLogo): string

Helper to generate URLs to Reyjavík's official coat of arms (or "logo"), with and without the text.

import { getRvkLogoUrl } from '@reykjavik/hanna-utils/assets';

const defaultLogoSVG = getRvkLogoUrl(); // default is 'reykjavik-logo.svg'
const defaultLogoPNG = getRvkLogoUrl('reykjavik-logo.png'); // PNG version
const notextLogoSVG = getRvkLogoUrl('reykjavik-logo-notext.svg');
// etc...

Here's a list of available logo files: reykjavik-logo.json

Favicons

Syntax: getFavicon(faviconFile: Favicon): string

Helper to generate URLs for various types of "favicons" or "webmanifest icons", etc...

import { getFavicon } from '@reykjavik/hanna-utils/assets';

const url = getFavicon('favicon.svg');

The function is typed to provide auto-completion of all the available icon types.

Here's a list of available logo files: reykjavik-logo.json

Illustrations

Syntax: getIllustrationUrl(illustration: Illustration, variant?: IllustrationVariant): string

Utilities to work with the Illustrations on the asset server.

import {
  illustrations,
  Illustration,
  getIllustrationUrl,
} from '@reykjavik/hanna-utils/assets';

const assetName: Illustration = illustrations[0];

const url = getIllustrationUrl(assetName);
const thumbnailUrl = getIllustrationUrl(assetName, 'thumb');

Efnistákn Icons

Syntax: getEfnistaknUrl(icon: Efnistakn): string

Utilities to work with the Efnistákn icons on the asset server.

import {
  efnistakn,
  Efnistakn,
  getEfnistaknUrl,
} from '@reykjavik/hanna-utils/assets';

const assetName: Efnistakn = efnistakn[0];

const url = getEfnistaknUrl(assetName);

Formheimur Shapes

Syntax: getFormheimurUrl(shape: Formheimur): string

Utilities to work with the Formheimur shapes on the asset server.

import {
  formheimur,
  Formheimur,
  getFormheimurUrl,
} from '@reykjavik/hanna-utils/assets';

const assetName: Formheimur = formheimur[0];

const url = getFormheimurUrl(assetName);

Bling Shapes

Syntax: getBlingUrl(blingType: BlingType): string

Utilities to work with the Bling shapes on the asset server.

import {
  blingTypes,
  BlingType,
  getBlingUrl,
} from '@reykjavik/hanna-utils/assets';

const blingName: BlingType = blingTypes[0];

const url = getBlingUrl(blingName);

Misc. Style Server Assets

Syntax: getAssetUrl(filePath: string): string

Helper to generate a URL to arbitrary asset on on the style server.

import { getAssetUrl } from '@reykjavik/hanna-utils/assets';

const url = getAssetUrl('reykjavik-logo.svg');

Style-Server Info

styleServerUrl

Syntax: styleServerUrl: string

This URL is used when building links to graphic/styling assets, etc. It is used internally by all of the above asset getter functions (getIllustrationUrl, getIllustrationUrl).

The default value depends on NODE_ENV:

setStyleServerUrl

Syntax: setStyleServerUrl(url: string | URL | undefined): void

This updates the value of styleServerUrl globally. Use it at the top of your application if you want to load assets and CSS bundles from a custom style-server instance, e.g. during testing/staging/etc.

The URLs are pushed to a simple stack, and if you want to unset a custom URL, use the setLinkRenderer.pop() method to revert back to the previous one. Example:

import {
  setStyleServerUrl,
  styleServerURL,
} from '@reykjavik/hanna-utils/assets';

setStyleServerUrl('https://styles.test.thon.is/');

console.log(styleServerURL); // 'https://styles.test.thon.is'
const illustrationUrl1 = getIllustrationUrl('esjan');
// 'https://styles.test.thon.is/assets/illustrations/esjan.png'

setStyleServerUrl.pop(); // reset `styleServerUrl` to previous value

console.log(styleServerURL); // 'https://styles.reykjavik.is'
const illustrationUrl = getIllustrationUrl('esjan');
// 'https://styles.reykjavik.is/assets/illustrations/esjan.png'

You can explicitly switch to using the library's default styleServerURL by passing undefined as an argument — like so:

setStyleServerUrl(undefined); // pushes the default URL to the stack

I18N helpers

getTexts

Syntax: <Texts extends Record<string, unknown>, Lang extends string>( props: { texts?: Texts; lang?: Lang }, defaultTexts: DefaultTexts<Texts, Lang>) => Readonly<Texts>

Helper for components that expose (optional) texts and lang props for customizing their UI texts,

Returns texts when available, but otherwise it resolves the correct texts object from within defaultTexts to use based on lang (falling back on DEFAULT_LANGUAGE texts or Icelandic when all else fails).

In dev-mode it emits an error to the console if an unsupported lang is passed.

import {
  getTexts,
  type DefaultTexts,
  type HannaLang,
} from '@reykjavik/hanna-utils/i18n';

type Props = {
  isOpen: boolean;
  onToggle: () => void;
  // I18n props:
  texts?: { open: string; close: string };
  lang?: HannaLang;
};

const defaultTexts: DefaultTexts<Props['texts']> = {
  is: { open: 'Opna', close: 'Loka' },
  en: { open: 'Open', close: 'Close' },
  pl: { open: 'Otworzyć', close: 'Zamknąć' },
};

export const SillyToggler = (props: Props) => {
  const texts = getTexts(props, defaultTexts);

  return (
    <button onClick={props.onToggle}>
      {props.isOpen ? texts.open : texts.close}
    </button>
  );
};

DEFAULT_LANG

Syntax: DEFAULT_LANG: HannaLang

All Hanna components that use getTexts will use this value as their default translation language.

ensureHannaLang

Syntax: ensureHannaLang(maybeLang: string|undefined): HannaLang | undefined

Checks if the passed language is a HannaLang, and if so returns it. Otherwise it returns undefined.

import { ensureHannaLang, type HannaLang } from '@reykjavik/hanna-utils/i18n';

const langQuery = new URLSearchParams(document.location.search).get('lang');

const lang: HannaLang | undefined = ensureHannaLang(langQuery);

setDefaultLanguage

Syntax: updateDefaultLanguage(lang: HannaLang): void

This sets the value of Hanna DEFAULT_LANG variable globally. Use it at the top of your application to match its locale.

The DEFAULT_LANG variable is NOT reactive, and does not trigger re-renders.

import {
  setDefaultLanguage,
  DEFAULT_LANG,
} from '@reykjavik/hanna-utils/i18n';

console.log(DEFAULT_LANG); // 'is' (Initial default language)

setDefaultLanguage('pl');
console.log(DEFAULT_LANG); // 'pl'

You can explicitly switch to using the library's initial DEFAULT_LANG by passing undefined as an argument — like so:

setStyleServerUrl(undefined); // pushes the initial language to the stack

setDefaultLanguage.push()

Syntax: setDefaultLanguage.push(lang: HannaLang): void

This function pushes a new language onto a simple stack. Use setDefaultLanguage.pop() to revert back to the previous one.

Example:

console.log(DEFAULT_LANG); // 'pl' (the language set in previous example)

setDefaultLanguage.push('pl');
console.log(DEFAULT_LANG); // 'en'

setDefaultLanguage.pop(); // reset `DEFAULT_LANG` to previous value
console.log(DEFAULT_LANG); // 'pl'

Social Media Sharing

Hanna-utils provides a small, easy to use suite of utilities to generate, GDPR and privacy-friendly social-media sharing links.

import * from '@reykjavik/hanna-utils/shareButtonsUtils';

Until proper documentation is ready, see shareButtonsUtils.ts (and ShareButtons.tsx for an example of how it's used in hanna-react).

Polyfills / A11y

focus-visible polyfill

Exposes focus-visible as an optionally importable module to consumers of hanna-utils, without requiring them to install it as a standalone dependency in their project.

At/near the top of your App do:

import '@reykjavik/hanna-utils/focus-visible';

Branded types

ensurePosInt

Syntax: ensurePosInt(cand: unknown): PositiveInteger | undefined

Checks if cand evaluates to a positive integer and, if so, returns a branded PositiveInteger of equal value.

Returns undefined otherwise.

Examples:

  • 11
  • "1"1
  • 0undefined
  • -1undefined
  • 1.5undefined
  • "Infinity"undefined
  • "foo"undefined

TypeScript helpers

notNully

Syntax: notNully(value: unknown): value is NonNullable<V>

Simple type-guarding filter function that filters out nully values (null and undefined) in a type-aware way.

import { notNully } from '@reykjavik/hanna-utils';

const mixed = ['hi', null, undefined, ''];
const strings: Array<string> = mixed.filter(notNully);
// ['hi', '']

notFalsy

Syntax: notFalsy(value: unknown): value is NonNullable<V>

Simple type-guarding filter function that filters out "falsy" values ("", 0, NaN, false, null and undefined) in a type-aware way.

import { notFalsy } from '@reykjavik/hanna-utils';

const mixed = ['hi', null, undefined, '', 0, false, 'ho'] as const;
const strings: Array<'hi' | 'ho'> = mixed.filter(notFalsy);
// ['hi', 'ho']

ObjectKeys, ObjectEntries, ObjectFromEntries

Nicer, more type-aware aliases for the native Object.keys, Object.entries and Object.fromEntries.

import {
  ObjectKeys,
  ObjectEntries,
  ObjectFromEntries,
} from '@reykjavik/hanna-utils';

Type OpenRecord

Syntax: OpenRecord<Keys extends string, Values>

A variant of Record<string, T> that warns if any Keys are missing when it's declared.

It is useful for building enum objects to quickly validate JavaScript run-time user inputs and applying default values and "alias" outdated keys.

import type { OpenRecord } from '@reykjavik/hanna-utils';

type SizeVariant = 'small' | 'large';
const sizes: OpenRecord<SizeVariant, number> = {
  // Required keys
  small: 12,
  large: 20,
  // Extra key
  normal: 16,
};

// ...

const sizeValue = sizes[props.size || 'normal'] || sizes.normal;

Type OpenStringMap

Syntax: OpenStringMap<Keys extends string, Values = Keys>

A variant of OpenRecord for cases where you're mapping Keys to themselves. It allows for shorter/simpler type signature. The second Value parameter is unioned to Keys

import type { OpenStringMap } from '@reykjavik/hanna-utils';

type AlignVariant = 'left' | 'right';

const aligns: OpenStringMap<AlignVariant> = {
  // Required keys
  left: 'left',
  right: 'right',
  // Extra key
  default: 'left', // value must be of type AlignVariant
};

const aligns2: OpenStringMap<AlignVariant, ''> = {
  // Required keys
  left: 'left',
  right: 'right',
  // Extra key
  default: '', // '' is explicitly allowed by the type signature
};

// ...

const alignValue = aligns[props.align || 'default'] || aligns.default;

Type AllowKeys

Return A with the unique keys of B as optionally undefined.

Example:

type A = { type: 'profit'; gain: number };
type B = { type: 'loss'; loss: number; panic: boolean };

type MyProps = AllowKeys<A, B>;

is equivalent to:

type MyProps = { type: 'profit'; gain: number; loss?: never; panic?: never };

The second type parameter can also be a union of strings. Thus, the above example could be rewritten so:

type MyProps = AllowKeys<A, 'type' | 'loss' | 'panic'>;

NOTE: This type helper is used by EitherObj<A,B,…> type.

Type EitherObj

Allow any one of its input types, but accept the keys from the other type(s) as optionally undefined.

The EitherObj accepts between 2 and 4 type parameters.

Example with three inputs: A, B and C:

type A = { type: 'profit'; gain: number };
type B = { type: 'loss'; loss: number };
type C = { type: 'even'; panic: boolean };

type MyProps = EitherObj<A, B, C>;

is equivalent to:

type MyProps =
  | { type: 'profit'; gain: number; loss?: never; panic?: never };
  | { type: 'loss'; gain?: never; loss: number; panic?: never };
  | { type: 'even'; gain?: never; loss?: never; panic: boolean };

Type OmitDistributive

A variant of Omit that distributes over unions.

See: TypeScript Playground

Type PickDistributive

A variant of Pick that distributes over unions.

See: TypeScript Playground

Type RequireExplicitUndefined

Converts a type so that all optional keys are required and must be explicitly set to undefined.

type Foo = { a: string; b?: number };

type Bar = RequireExplicitUndefined<Foo>;

Is equivalent to:

type Bar = { a: string; b: number | undefined };

Type Testing Helpers

Type Expect<T>

Expects T to be true

import type { Expect } from '@reykjavik/hanna-utils';

type OK = Expect<true>;
type Fails = Expect<false>; // Type Error

Type Equals<A, B>

Returns true if types A and B are equal (and neither is any)

import type { Equals, Expect } from '@reykjavik/hanna-utils';

type OK = Expect<Equals<'same', 'same'>>;
type Fails = Expect<Equals<'not', 'same'>>; // Type Error

Type Extends<A, B>

Returns true if type A extends type B (and neither is any)

import type { Extends, Expect } from '@reykjavik/hanna-utils';

type OK = Expect<Extends<'some', string>>;
type Fails = Expect<Extends<string, 'some'>>; // Type Error

Type NotExtends<A, B>

Returns true if type A does NOT extend type B (and neither is any)

import type { NotExtends, Expect } from '@reykjavik/hanna-utils';

type OK = Expect<NotExtends<string, 'some'>>;
type Fails = Expect<NotExtends<'some', string>>; // Type Error
type FailsAlso = Expect<NotExtends<'same', 'same'>>; // Type Error

Changelog

See CHANGELOG.md