@reykjavik/hanna-utils v0.2.21
@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-utilsTable 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 2022getStableRandomItem
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:
- Production mode:
https://styles.reykjavik.is - Dev mode:
https://styles.test.thon.is
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
console.log(styleServerURL); // 'https://styles.reykjavik.is'Similarly the token 'test' is an alias for the default test server URL.
setStyleServerUrl('test'); // pushes the default test server URL to the stack
console.log(styleServerURL); // 'https://styles.test.thon.is'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 stacksetDefaultLanguage.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).
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:
1→1"1"→10→undefined-1→undefined1.5→undefined"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.
Type PickDistributive
A variant of Pick that distributes over unions.
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 Fails = Expect<'Error message'>; // Type ErrorType NotExpect<T>
Expects T to be false or a string (as returned by the Equals,
Extends and NotExtends helpers).
import type { NotExpect } from '@reykjavik/hanna-utils';
type OK1 = NotExpect<false>;
type OK2 = NotExpect<'Error message'>;
type Fails = NotExpect<true>; // Type ErrorType 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 ErrorType 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 ErrorType 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 ErrorChangelog
See CHANGELOG.md
8 months ago
10 months ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago