Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/mathieuprog/solid-compose
A set of reactive state for commonly used features in web apps
https://github.com/mathieuprog/solid-compose
solidjs
Last synced: about 2 months ago
JSON representation
A set of reactive state for commonly used features in web apps
- Host: GitHub
- URL: https://github.com/mathieuprog/solid-compose
- Owner: mathieuprog
- License: apache-2.0
- Created: 2022-07-23T03:03:13.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2023-11-24T07:15:15.000Z (about 1 year ago)
- Last Synced: 2024-05-01T16:00:18.379Z (8 months ago)
- Topics: solidjs
- Language: TypeScript
- Homepage:
- Size: 23.1 MB
- Stars: 8
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
# Solid Compose
`solid-compose` provides a set of reactive state for commonly used features in web apps.
Currently, it includes
* [internationalization (i18n)](#internationalization-i18n)
* [localStorage (client-side storage)](#localstorage-client-side-storage)
* [localization (l10n)](#localization-l10n)
* [color scheme (dark, light mode)](#color-scheme-dark-light-mode)
* [language tag](#language-tag)
* [number format](#number-format)
* [date format](#date-format)
* [time format](#time-format)
* [time zone](#time-zone)
* [first day of the week](#first-day-of-the-week)
* [text direction](#text-direction)
* [theming](#theming)
* [viewport](#viewport)
* [authentication](#authentication)
* [developer utilities](#developer-utilities)## Internationalization (i18n)
Solid Compose provides i18n support allowing to build multilingual apps.
First, add your app's translations:
```typescript
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:
```typescript
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.Example using keySeparator
```typescript
addTranslations('fr', {
"welcome": {
"hello": "Bonjour !"
}
});createI18nPrimitive({
fallbackLanguageTag: 'en',
keySeparator: '.'
});translate('welcome.hello') // Bonjour !
```
Translate your app:```typescript
import { useI18n, useLocale } from 'solid-compose';function Hello() {
const [locale, { setLanguageTag }] = useLocale();
const translate = useI18n();return <>
{translate('hello', { name: 'John' })}
Current locale: {locale.languageTag}
Switch locale: {setLanguageTag('fr')}
>;
}
```You may also have objects as parameters:
```typescript
addTranslations('en' {
"hello": "Hello, {{ user.name }}!"
});function Hello() {
const translate = useI18n();return <>
{translate('hello', { user: { name: 'John' }})}
>;
}
```To translate in a given language rather than the current locale, you may pass the locale as a third argument:
```typescript
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:
```typescript
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:
```typescript
translate('messages', { cardinal: 1 }); // One message received.
``````typescript
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:
```typescript
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:
```typescript
import { I18nProvider } from 'solid-compose';render(() =>
<>
>
);
```## localStorage (client-side storage)
Solid Compose makes localStorage values reactive:
```typescript
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:
```typescript
{
serialize: JSON.stringify,
deserialize: JSON.parse
}
```## Localization (l10n)
Solid Compose stores user locale parameters into a store.
First, initialize and configure the locale primitive:
```typescript
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:
```typescript
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.
How is the initial language tag determined?
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:
```typescript
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).
```jsx
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:
```css
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.
```jsx
import { ColorSchemeStylesheet } from 'solid-compose';const App: VoidComponent = () => {
return (
<>
…
>
);
};
```In addition to adding the necessary stylesheets,
`setColorScheme` allows to switch the color scheme:
```typescript
import { useLocale } from 'solid-compose';const [locale, { setColorScheme }] = useLocale();
```The initial color scheme is derived from the [system or user agent](https://developer.mozilla.org/docs/Web/CSS/@media/prefers-color-scheme), 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](#theming).
### Language tag
`setLanguageTag` allows to change the language:
```typescript
import { useLocale } from 'solid-compose';const [locale, { setLanguageTag }] = useLocale();
````getClosestSupportedLanguageTag` allows to find the best language supported by your app:
```typescript
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:
```typescript
import { useLocale } from 'solid-compose';const [locale, { setNumberFormat }] = useLocale();
````formatNumber` allows to format a number according to the current locale's number formatting setting.
```typescript
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.
```typescript
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:
```typescript
import { useLocale } from 'solid-compose';const [locale, { setDateFormat }] = useLocale();
````formatDate` allows to format a date according to the current locale's date formatting setting.
```typescript
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:
```typescript
import { useLocale } from 'solid-compose';const [locale, { setTimeFormat }] = useLocale();
````formatTime` allows to format a time according to the current locale's time formatting setting.
```typescript
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:
```typescript
import { useLocale } from 'solid-compose';const [locale, { setTimeZone }] = useLocale();
```### First day of the week
`setFirstDayOfWeek` allows to change the first day of the week:
```typescript
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:
```typescript
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):
```typescript
createLocalePrimitive({ supportedLanguageTags: ['en'] });
```Then, initialize and configure the theme primitive:
```typescript
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:
```typescript
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:
```jsx
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:
```css
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:
```typescript
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.
```jsx
import { ThemeStylesheet } from 'solid-compose';const App: VoidComponent = () => {
return (
<>
…
>
);
};
````setTheme` allows to switch the theme:
```typescript
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:
```typescript
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:
```typescript
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:
```typescript
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:
```jsx
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.
```jsx
import { createCurrentUserPrimitive } from 'solid-compose';const createCurrentUserResource = () => createResource(() => /* ... */);
createCurrentUserPrimitive({
createCurrentUserResource,
isUnauthenticatedError: (error: any) => isUnauthenticatedError(error),
isAuthenticated: (data: unknown) => data.__typename === 'User'
});const [currentUser, { authenticationStatus, authenticationError }] = useCurrentUser();
```
## 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 weekTo 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.
```typescript
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](http://npmjs.com).
```
npm install solid-compose
```