driver
A typescript package for declaring finite states and commonly derived value...
README
🏁 driver
✨ Features
📦 Installation
- ```bash
- $ npm i @switz/driver
- ```
🍬 Sample Code
- ```javascript
- import driver from '@switz/driver';
- const CheckoutButton = ({ cartData, isLoading, checkout }) => {
- const shoppingCart = driver({
- // the first state to return true is the active state
- states: {
- isLoading,
- isCartEmpty: cartData.items.length === 0,
- isCartInvalid: !!cartData.isError,
- isCartValid: true, // fallback/default
- },
- derived: {
- // arrays resolve to a boolean (true) if the active state
- // matches a state key in the array
- isDisabled: ['isLoading', 'isCartEmpty', 'isCartInvalid'],
- // objects resolve to whichever value is specified as
- // the currently active state
- intent: {
- isLoading: 'info',
- isCartEmpty: 'info',
- isCartInvalid: 'error',
- isCartValid: 'primary',
- },
- },
- });
- return (
- <Button
- icon="checkout"
- intent={shoppingCart.intent}
- disabled={shoppingCart.isDisabled}
- onClick={checkout}
- >
- Checkout
- </Button>
- );
- }
- ```
- ```js
- shoppingCart.isDisabled => true
- shoppingCart.intent => 'info'
- ```
- ```js
- shoppingCart.isDisabled => false
- shoppingCart.intent => 'primary'
- ```
👩🏭 Basic Introduction
- ```javascript
- import driver from '@switz/driver';
- const CheckoutButton = ({ cartData }) => {
- const button = driver({
- states: {
- isEmpty: cartData.items.length === 0,
- canCheckout: cartData.items.length > 0,
- },
- derived: {
- // if the active state matches any strings in the array, `isDisabled` returns true
- isDisabled: ['isEmpty'],
- },
- });
- return (
- <Button icon="checkout" disabled={button.isDisabled} onClick={onClick}>
- Checkout
- </Button>
- );
- }
- ```
States | isDisabled |
---|---|
------------- | ------------- |
isEmpty | true |
canCheckout | false |
- ```javascript
- const CheckoutButton = ({ cartItems }) => {
- const isEmpty = cartItems.length === 0;
- return (
- <Button icon="checkout" disabled={isEmpty} onClick={onClick}>
- Checkout
- </Button>
- );
- }
- ```
- ```javascript
- const CheckoutButton = ({ cartItems, isLoading, checkout }) => {
- const cartValidation = validation(cartItems);
- const shoppingCart = driver({
- states: {
- isLoading,
- isCartEmpty: cartItems.length === 0,
- isCartInvalid: !!cartValidation.isError,
- isCartValid: true, // fallback/default
- },
- derived: {
- popoverText: {
- // unspecified states (isLoading, isCartValid here) default to undefined
- isCartEmpty: 'Your shopping cart is empty, add items to checkout',
- isCartInvalid: 'Your shopping cart has errors: ' + cartValidation.errorText,
- },
- buttonVariant: {
- isLoading: 'info',
- isCartEmpty: 'info',
- isCartInvalid: 'error',
- isCartValid: 'primary',
- },
- // onClick will be undefined except `ifCartValid` is true
- //
- onClick: {
- isCartValid: checkout,
- }
- },
- });
- return (
- <Popover content={shoppingCart.popoverText} disabled={!shoppingCart.popoverText}>
- <Button icon="checkout" intent={shoppingCart.buttonVariant} disabled={!shoppingCart.onClick} onClick={shoppingCart.onClick}>
- Checkout
- </Button>
- </Popover>
- );
- }
- ```
States | popoverText | buttonVariant | onClick |
---|---|---|---|
------------- | ------------- | ------------- | ------------- |
isLoading | | | | | |
isCartEmpty | "Your | info | | |
isCartInvalid | "Your | error | | |
isCartValid | | | () |
🖼️ Background
- ```javascript
- const CheckoutButton = ({ cartItems, isLoading }) => {
- const cartValidation = validation(cartItems);
- let popoverText = 'Your shopping cart is empty, add items to checkout';
- let buttonVariant = 'info';
- let isDisabled = true;
- if (cartValidation.isError) {
- popoverText = 'Your shopping cart has errors: ' + cartValidation.errorText;
- buttonVariant = 'error';
- }
- else if (cartValidation.hasItems) {
- popoverText = null;
- isDisabled = false;
- buttonVariant = 'primary';
- }
- return (
- <Popover content={popoverText} disabled={!popoverText}>
- <Button icon="checkout" intent={buttonVariant} disabled={isLoading || isDisabled} onClick={checkout}>
- Checkout
- </Button>
- </Popover>
- );
- }
- ```
Other examples:
- ```javascript
- const DownloadButton = ({ match }) => {
- const demoButton = driver({
- states: {
- isNotRecorded: !!match.config.dontRecord,
- isUploading: !match.demo_uploaded,
- isUploaded: !!match.demo_uploaded,
- },
- derived: {
- isDisabled: ['isNotRecorded', 'isUploading'],
- // could also write this as:
- // isDisabled: (states) => states.isNotRecorded || states.isUploading,
- text: {
- isNotRecorded: 'Demo Disabled',
- isUploading: 'Demo Uploading...',
- isUploaded: 'Download Demo',
- },
- },
- });
- return (
- <Button icon="download" disabled={!!demoButton.isDisabled}>
- {demoButton.text}
- </Button>
- );
- }
- ```
👾 Docs
- ```javascript
- driver({
- states: {
- state1: false,
- state2: true,
- },
- derived: {
- text: {
- state1: 'State 1!',
- state2: 'State 2!',
- }
- }
- })
- ```
States
- ```javascript
- driver({
- states: {
- isNotRecorded: match.config.dontRecord,
- isUploading: !match.demo_uploaded,
- isUploaded: match.demo_uploaded,
- },
- });
- ```
Derived
Function
- ```diff
- driver({
- states: {
- isNotRecorded: match.config.dontRecord,
- isUploading: !match.demo_uploaded,
- isUploaded: match.demo_uploaded,
- },
- + derived: {
- + isDisabled: (states) => states.isNotRecorded || states.isUploading,
- + }
- })
- ```
- ```diff
- driver({
- states: {
- isNotRecorded: match.config.dontRecord,
- isUploading: !match.demo_uploaded,
- isUploaded: match.demo_uploaded,
- },
- derived: {
- + isDisabled: (_, stateEnums, activeEnum) => (activeEnum ?? 0) <= stateEnums.isUploading,
- }
- })
- ```
Array
- ```diff
- driver({
- states: {
- isNotRecorded: match.config.dontRecord,
- isUploading: !match.demo_uploaded,
- isUploaded: match.demo_uploaded,
- },
- derived: {
- + isDisabled: ['isNotRecorded', 'isUploading'],
- }
- })
- ```
Object Lookup
- ```diff
- driver({
- states: {
- isNotRecorded: match.config.dontRecord,
- isUploading: !match.demo_uploaded,
- isUploaded: match.demo_uploaded,
- },
- derived: {
- + text: {
- + isNotRecorded: 'Demo Disabled',
- + isUploading: 'Demo Uploading...',
- + isUploaded: 'Download Demo',
- + },
- }
- })
- ```
Svelte Example
- ```javascript
- <script>
- import driver from "@switz/driver";
- let count = 0;
- function handleClick() {
- count += 1;
- }
- // use $ to mark our driver as reactive
- $: buttonInfo = driver({
- states: {
- IS_ZERO: count === 0,
- IS_TEN: count >= 10,
- IS_MORE: count >= 0
- },
- derived: {
- text: {
- IS_ZERO: "Click me to get started",
- IS_MORE: `Clicked ${count} ${count === 1 ? "time" : "times"}`,
- IS_TEN: "DONE!"
- },
- isDisabled: ["IS_TEN"]
- }
- });
- </script>
- <button on:click={handleClick} disabled={buttonInfo.isDisabled}>
- {buttonInfo.text}
- </button>
- ```
Key Ordering Consistency
Property order in normal Objects is a complex subject in JavaScript.
While in ES5 explicitly no order has been specified, ES2015 defined an order in certain cases, and successive changes to the specification since have increasingly defined the order (even, as of ES2020, the for-in loop's order).
This results in the following order (in certain cases):
Object {
0: 0,
1: "1",
2: "2",
b: "b",
a: "a",
m: function() {},
Symbol(): "sym"
}
The order for "own" (non-inherited) properties is:
Positive integer-like keys in ascending order
String keys in insertion order
Symbols in insertion order
https://tc39.es/ecma262/#sec-ordinaryownpropertykeys
Help and Support
Warning: this is naive and changing
Typescript
Local Development
- ```bash
- bun install
- ```
- ```bash
- npm run test # we test the typescript types on top of basic unit tests
- ```