0.2.15 • Published 1 month ago

@reykjavik/hanna-utils v0.2.15

Weekly downloads
-
License
MIT
Repository
github
Last release
1 month ago

@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"

Asset helpers

Reykjavík Logo

Syntax: getRvkLogoUrl(blingType: BlingType): 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...

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.

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

0.2.15

1 month ago

0.2.14

3 months ago

0.2.13

5 months ago

0.2.12

8 months ago

0.2.11

8 months ago

0.2.10

8 months ago

0.2.7

10 months ago

0.2.9

8 months ago

0.2.8

10 months ago

0.2.6

11 months ago

0.2.5

12 months ago

0.2.4

12 months ago

0.2.3

1 year ago

0.2.2

1 year ago

0.2.1

1 year ago

0.2.0

1 year ago

0.1.18

1 year ago

0.1.15

2 years ago

0.1.16

2 years ago

0.1.17

1 year ago

0.1.11

2 years ago

0.1.12

2 years ago

0.1.13

2 years ago

0.1.14

2 years ago

0.1.10

2 years ago

0.1.8

2 years ago

0.1.7

2 years ago

0.1.9

2 years ago

0.1.6

2 years ago

0.1.5

2 years ago

0.1.4

2 years ago

0.1.3

2 years ago

0.1.2

2 years ago

0.1.1

2 years ago

0.1.0

2 years ago