solid-compose
v0.44.0
Published
A set of reactive state for commonly used features in web apps
Downloads
58
Readme
Solid Compose
solid-compose
provides a set of reactive state for commonly used features in web apps.
Currently, it includes
- internationalization (i18n)
- localStorage (client-side storage)
- localization (l10n)
- theming
- viewport
- authentication
- developer utilities
Internationalization (i18n)
Solid Compose provides i18n support allowing to build multilingual apps.
First, add your app's translations:
import { addTranslations } from 'solid-compose';
addTranslations('en' {
"hello": "Hello, {{ name }}!"
});
addTranslations('fr' {
"hello": "Bonjour, {{ name }} !",
});
// from JSON files
// (make sure to have TS config "resolveJsonModule" set to true)
import enTranslations from './translations/en.json';
import frTranslations from './translations/fr.json';
addTranslations('en', enTranslations);
addTranslations('fr', frTranslations);
Then initialize and configure the locale and i18n global primitives:
import {
createI18nPrimitive,
createLocalePrimitive,
getSupportedLanguageTags
} from 'solid-compose';
createLocalePrimitive({
// getSupportedLanguageTags() returns the language tags
// for which translations exist
supportedLanguageTags: getSupportedLanguageTags()
});
createI18nPrimitive({
fallbackLanguageTag: 'en'
});
createI18nPrimitive
accepts 2 optional configuration params:
fallbackLanguageTag
: the locale to fallback to if no translation is found for the current locale;keySeparator
: allows to have nested translations.
addTranslations('fr', {
"welcome": {
"hello": "Bonjour !"
}
});
createI18nPrimitive({
fallbackLanguageTag: 'en',
keySeparator: '.'
});
translate('welcome.hello') // Bonjour !
import { useI18n, useLocale } from 'solid-compose';
function Hello() {
const [locale, { setLanguageTag }] = useLocale();
const translate = useI18n();
return <>
<div>{translate('hello', { name: 'John' })}</div>
<div>Current locale: {locale.languageTag}</div>
<div>Switch locale: {setLanguageTag('fr')}</div>
</>;
}
You may also have objects as parameters:
addTranslations('en' {
"hello": "Hello, {{ user.name }}!"
});
function Hello() {
const translate = useI18n();
return <>
<div>{translate('hello', { user: { name: 'John' }})}</div>
</>;
}
To translate in a given language rather than the current locale, you may pass the locale as a third argument:
translate('hello', { user: { name: 'John' }}, 'fr')
Pluralization support
Languages have different rules for plurals.
Solid Compose allows you to define a translation per plural rule:
addTranslations('en', {
"messages": {
"one": "One message received.",
"other": "{{ cardinal }} messages received.",
"zero": "No messages received."
},
"position": {
"one": "{{ ordinal }}st",
"two": "{{ ordinal }}nd",
"few": "{{ ordinal }}rd",
"other": "{{ ordinal }}th",
}
});
Either a cardinal
or ordinal
parameter must be present when translating, for the library to pick the right message:
translate('messages', { cardinal: 1 }); // One message received.
translate('position', { ordinal: 1 }); // 1st
Namespaces
Namespaces allow to load only a subset of the available translations, which eases the handling of key collisions in larger apps.
Say for instance that your application is made of multiple sub-apps, you may have a "todo" namespace including the translations for the todo sub-app, a "scheduler" namespace for the scheduler sub-app, etc.
addTranslations
optionally accepts as second argument a namespace:
addTranslations('en', {
// common translations
});
addTranslations('en', 'todo', {
// translations for the todo app
});
addTranslations('en', 'scheduler', {
// translations for the scheduler app
});
addTranslations('en', 'time', {
// translations related to time
});
You may then use the I18nProvider
component to scope the translations per namespace:
import { I18nProvider } from 'solid-compose';
render(() =>
<>
<I18nProvider namespaces={['todo']}>
<TodoApp/>
</I18nProvider>
<I18nProvider namespaces={['time', 'scheduler']}>
<SchedulerApp/>
</I18nProvider>
</>
);
localStorage (client-side storage)
Solid Compose makes localStorage values reactive:
import { useLocalStorage } from 'solid-compose';
const [value, { set: setValue, remove: removeValue }] =
useLocalStorage('myKey', 'defaultValue');
useLocalStorage
accepts as 3rd argument an object containing the functions serializing and deserializing values to be stored and to be retrieved.
By default, the following object is used:
{
serialize: JSON.stringify,
deserialize: JSON.parse
}
Localization (l10n)
Solid Compose stores user locale parameters into a store.
First, initialize and configure the locale primitive:
import { createLocalePrimitive } from 'solid-compose';
createLocalePrimitive({
supportedLanguageTags: ['en', 'de', 'fr'],
defaultLanguageTag: 'de'
});
The supportedLanguageTags
configuration field is mandatory and specifies which language tags are supported by your application. The defaultLanguageTag
field is optional and utilized if the user's preferred language tag is not included in supportedLanguageTags
field.
You may then access the locale parameters:
import { useLocale } from 'solid-compose';
const [locale] = useLocale();
console.log(locale.languageTag);
console.log(locale.textDirection);
console.log(locale.numberFormat);
console.log(locale.timeZone);
console.log(locale.dateFormat);
console.log(locale.timeFormat);
console.log(locale.firstDayOfWeek);
console.log(locale.colorScheme);
All of those parameters are reactive.
The library looks for a language tag that is both supported by your application (in supportedLanguageTags
configuration) and listed in the user's browser as one of their preferred language tags (in navigator.languages
).
If a matching language tag cannot be found, the optional defaultLanguageTag
configuration is utilized. If not provided, either an English language tag is used or the first language tag in the list of supported language tags.
When you have information about the user's preferences, you can use it to initialize the locale data:
import { createLocalePrimitive } from 'solid-compose';
createLocalePrimitive({
supportedLanguageTags: ['en', 'de', 'fr'],
defaultLanguageTag: 'de',
initialValues: {
languageTag: user.languageTag,
numberFormat: user.numberFormat,
timeZone: user.timeZone,
dateFormat: user.dateFormat,
timeFormat: user.timeFormat,
firstDayOfWeek: user.firstDayOfWeek,
colorScheme: user.colorScheme
}
});
Every field in initialValues
is optional and if not provided, the value is inferred from the user's browser and system parameters.
Color scheme (dark, light mode)
Solid Compose provides color scheme toggling (light vs dark mode).
import { createColorSchemeEffect } from 'solid-compose';
createColorSchemeEffect();
The effect includes the color-scheme
meta tag, the CSS styling property color-scheme
on the html tag, as well as a data attribute "data-color-scheme"
on the html tag.
The data attribute enables the selection of CSS selectors based on the color scheme, allowing you to set CSS variables for the current color scheme:
html[data-color-scheme='dark'] {
--primary-text-color: var(--grey-200);
--secondary-text-color: var(--grey-500);
}
If you wish to include external files instead of including the theming css in the css bundle, you may also add the ColorSchemeStylesheet
component in your app which will pick the right stylesheet according to the current color scheme.
import { ColorSchemeStylesheet } from 'solid-compose';
const App: VoidComponent = () => {
return (
<>
<ColorSchemeStylesheet
dark="./css/themes/dark-theme.css"
light="./css/themes/light-theme.css"
/>
<div>…</div>
</>
);
};
In addition to adding the necessary stylesheets,
setColorScheme
allows to switch the color scheme:
import { useLocale } from 'solid-compose';
const [locale, { setColorScheme }] = useLocale();
The initial color scheme is derived from the system or user agent, unless the initialValues
include a colorScheme
property.
If you intend to incorporate additional themes beyond just the dark and light modes, refer to the Theming.
Language tag
setLanguageTag
allows to change the language:
import { useLocale } from 'solid-compose';
const [locale, { setLanguageTag }] = useLocale();
getClosestSupportedLanguageTag
allows to find the best language supported by your app:
import { getClosestSupportedLanguageTag } from 'solid-compose';
createLocalePrimitive({ supportedLanguageTags: ['en-US', 'fr'] });
getClosestSupportedLanguageTag('en') // 'en-US'
getClosestSupportedLanguageTag('fr-BE') // 'fr'
getClosestSupportedLanguageTag('es') // 'en-US'
Number format
setNumberFormat
allows to change the number format:
import { useLocale } from 'solid-compose';
const [locale, { setNumberFormat }] = useLocale();
formatNumber
allows to format a number according to the current locale's number formatting setting.
import { formatNumber, useLocale } from 'solid-compose';
const [locale, { setNumberFormat }] = useLocale();
setNumberFormat(NumberFormat.SpaceComma);
formatNumber(1000.01) // 1 000,01
parseNumber
allows to parse a localized number string according to the current locale's number formatting setting.
import { parseNumber, useLocale } from 'solid-compose';
const [locale, { setNumberFormat }] = useLocale();
setNumberFormat(NumberFormat.SpaceComma);
parseNumber('1 000,01') // 1000.01
Date format
setDateFormat
allows to change the date format:
import { useLocale } from 'solid-compose';
const [locale, { setDateFormat }] = useLocale();
formatDate
allows to format a date according to the current locale's date formatting setting.
import { formatDate, useLocale } from 'solid-compose';
const [locale, { setDateFormat }] = useLocale();
setDateFormat({ endianness: DateEndianness.MiddleEndian });
formatDate(Temporal.PlainDate.from('2000-12-31')) // 12/31/2000
Time format
setTimeFormat
allows to change the time format:
import { useLocale } from 'solid-compose';
const [locale, { setTimeFormat }] = useLocale();
formatTime
allows to format a time according to the current locale's time formatting setting.
import { formatTime, useLocale } from 'solid-compose';
const [locale, { setTimeFormat }] = useLocale();
setTimeFormat({ is24HourClock: false });
formatTime(Temporal.PlainTime.from('00:30:05'), { precision: 'minute', omitZeroUnits: true }) // 12:30 AM
Time zone
setTimeZone
allows to change the time zone:
import { useLocale } from 'solid-compose';
const [locale, { setTimeZone }] = useLocale();
First day of the week
setFirstDayOfWeek
allows to change the first day of the week:
import { useLocale } from 'solid-compose';
const [locale, { setFirstDayOfWeek }] = useLocale();
Text direction
createTextDirectionEffect
function allows the text direction of the entire page to be changed by setting the dir
attribute on the html
tag to "ltr"
or "rtl"
based on the current locale:
import { createTextDirectionEffect } from 'solid-compose';
createTextDirectionEffect();
Theming
Solid Compose provides theming support.
First, initialize and configure the locale primitive (in order to fetch the user's preferred color scheme):
createLocalePrimitive({ supportedLanguageTags: ['en'] });
Then, initialize and configure the theme primitive:
createThemePrimitive({
themes: [
{
name: 'fooTheme',
colorScheme: ColorScheme.Dark,
default: true
},
{
name: 'barTheme',
colorScheme: ColorScheme.Light,
default: true
},
{
name: 'bazTheme',
colorScheme: ColorScheme.Dark
}
]
});
The initial theme is selected according to the user's preferred color scheme. You therefore need to specify one default dark theme and one default light theme.
In cases where the theme is based on a color that is not distinctly dark or light, it is still needed to specify a default color scheme in case of missing styles.
When you know the user's preferred theme, you don't need to specify any defaults and instead you may set the user's theme via the initialTheme
config:
createThemePrimitive({
themes: [
{
name: 'fooTheme',
colorScheme: ColorScheme.Dark
},
{
name: 'barTheme',
colorScheme: ColorScheme.Light
},
{
name: 'bazTheme',
colorScheme: ColorScheme.Dark
}
],
initialTheme: 'fooTheme'
});
You may then call the effect:
import { createThemeEffect } from 'solid-compose';
createThemeEffect();
The effect includes the color-scheme
meta tag, the CSS styling property color-scheme
on the html tag, as well as the data attributes "data-color-scheme"
and "data-theme"
on the html tag.
The data attributes enable the selection of CSS selectors based on the selected theme or current color scheme, allowing you to set CSS variables for the current theme:
html[data-theme='my-theme'] {
--primary-text-color: var(--grey-200);
--secondary-text-color: var(--grey-500);
}
html[data-color-scheme='dark'] {
--primary-text-color: var(--grey-200);
--secondary-text-color: var(--grey-500);
}
If you wish to include external files instead of including the theming css in the css bundle, you may also add the ThemeStylesheet
component in your app which will pick the right stylesheet according to the selected theme. In this case you have to specify the paths to the css files:
createThemePrimitive({
themes: [
{
name: 'fooTheme',
path: 'https://example.com/themes/foo.css',
colorScheme: ColorScheme.Dark
},
{
name: 'barTheme',
path: 'https://example.com/themes/bar.css',
colorScheme: ColorScheme.Light
},
{
name: 'bazTheme',
path: 'https://example.com/themes/baz.css',
colorScheme: ColorScheme.Dark
}
],
initialTheme: 'fooTheme'
});
You may then add the ThemeStylesheet
component in your app which will pick the right stylesheet according to the selected theme.
import { ThemeStylesheet } from 'solid-compose';
const App: VoidComponent = () => {
return (
<>
<ThemeStylesheet />
<div>…</div>
</>
);
};
setTheme
allows to switch the theme:
import { useTheme } from 'solid-compose';
const [theme, setTheme] = useTheme();
Viewport
Solid Compose allows to listen for changes to the viewport dimension and orientation.
First, initialize and configure the viewport primitive:
import { createViewportPrimitive } from 'solid-compose';
createViewportPrimitive({
widthSwitchpoints: {
small: { // width < 768
max: 768
},
medium: { // 768 <= width < 1280
min: 768,
max: 1280
},
large: { // 1280 <= width
min: 1280
},
}
});
createViewportPrimitive()
allows you to configure two properties: widthSwitchpoints
and heightSwitchpoints
. Both of these properties are objects that let you define custom size names and corresponding size ranges for the viewport dimensions.
The keys in each object represent the custom name for the size, while the values are sub-objects containing min
and/or max
allowing you to configure the switchpoints for either width or height.
You may then get the current viewport size and orientation and listen for changes:
import { useViewport, ViewportOrientation } from 'solid-compose';
const viewport = useViewport();
console.log(viewport.width); // "large"
console.log(viewport.height); // undefined (height switchpoint names not defined in the config object)
console.log(viewport.orientation === ViewportOrientation.Landscape);
You may define your custom switchpoints names with TypeScript enums in order to catch errors when comparing width and height values:
export enum Viewport {
SmallWidth = 'SMALL_WIDTH',
MediumWidth = 'MEDIUM_WIDTH',
LargeWidth = 'LARGE_WIDTH'
}
createViewportPrimitive({
widthSwitchpoints: {
[Viewport.SmallWidth]: {
max: 768
},
[Viewport.MediumWidth]: {
min: 768,
max: 1280
},
[Viewport.LargeWidth]: {
min: 1280
},
}
});
const viewport = useViewport();
if (viewport.width === Viewport.SmallWidth) {
// ...
}
You may call the effect:
import { createViewportEffect } from 'solid-compose';
createViewportEffect();
The effect sets the data attributes "data-viewport-width-switchpoint"
, "data-viewport-height-switchpoint"
and "data-viewport-orientation"
on the html tag.
Authentication
Solid Compose provides a primitive for making the current user's information accessible across the application.
import { createCurrentUserPrimitive } from 'solid-compose';
const createCurrentUserResource = () => createResource<CurrentUser>(() => /* ... */);
createCurrentUserPrimitive({
createCurrentUserResource,
isUnauthenticatedError: (error: any) => isUnauthenticatedError(error),
isAuthenticated: (data: unknown) => data.__typename === 'User'
});
const [currentUser, { authenticationStatus, authenticationError }] = useCurrentUser<CurrentUser>();
<Switch>
<Match when={currentUser.loading}>
<Loader />
</Match>
<Match when={authenticationStatus() === AuthenticationStatus.Unauthenticated}>
<Navigate href={'/login'} />
</Match>
<Match when={authenticationStatus() === AuthenticationStatus.Authenticated}>
<Outlet />
</Match>
</Switch>
Developer utilities
addLocaleHotkeyListener()
simplifies the testing of web interfaces that support multiple locale settings by allowing developers to quickly switch between different locales using customizable hotkeys. This enables you to switch between the following settings with ease:
- Color schemes
- Themes
- Languages
- Text directions
- Number formats
- Time zones
- Date formats
- Time formats
- First day of the week
To use addLocaleHotkeyListener(), simply pass in the hotkeys as optional functions, as shown in the code snippet below. Each hotkey is optional, so if you don't need to test a particular locale setting, simply omit it.
import { addLocaleHotkeyListener } from 'solid-compose';
addLocaleHotkeyListener({
hotkeys: {
colorScheme: (e) => e.shiftKey && e.code === 'KeyQ',
theme: (e) => e.shiftKey && e.code === 'KeyW',
languageTag: (e) => e.shiftKey && e.code === 'KeyA',
textDirection: (e) => e.shiftKey && e.code === 'KeyS',
numberFormat: (e) => e.shiftKey && e.code === 'KeyD',
timeZone: (e) => e.shiftKey && e.code === 'KeyZ',
dateFormat: (e) => e.shiftKey && e.code === 'KeyX',
timeFormat: (e) => e.shiftKey && e.code === 'KeyC',
firstDayOfWeek: (e) => e.shiftKey && e.code === 'KeyV',
},
timeZones: ['Asia/Bangkok', 'Europe/London']
});
Install
You can get solid-compose
via npm.
npm install solid-compose