Skip to content

Commit

Permalink
Docs and minor changes
Browse files Browse the repository at this point in the history
  • Loading branch information
aryzing committed Mar 22, 2024
1 parent 38a628e commit 25cf924
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 26 deletions.
23 changes: 6 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# `@sats-connect/ui`

A Web3 wallet provider selector. Built as a custom element, compatible with all major UI frameworks.
A Bitcoin Web3 wallet provider selector. Built as a custom element, compatible with all major UI frameworks.

## Basic usage

```ts
import { registerElement, selectWalletProvider } from "@sats-connect/ui";
import { loadSelector, selectWalletProvider } from "@sats-connect/ui";

// Call this only once in your app, loads the custom element.
registerElement();
// Call this once in your app, loads the custom element.
loadSelector();

// At a later point,
//
Expand Down Expand Up @@ -50,17 +50,6 @@ npm run build-app

The latest version of the example app is available at <https://sats-connect-ui.netlify.app>.

# Arch
# Docs

- Using Solid.js: no runtime, compiler
- [Web component buider](https://github.com/solidjs/solid/tree/main/packages/solid-element#readme) (preserves reactivity)
- Styles inline b/c no style frameworks work to build custom elements
- [CSS reset](https://github.com/sindresorhus/modern-normalize/blob/main/modern-normalize.css) adapted with [`:host` as suggested here](https://www.colorglare.com/css-resets-and-global-styles-in-web-components-c71fcea86dbd).
- No portals, or styles break
- Globally added `DM Sans` to reset as required by designs.
- A `<link>` tag is added to document head on component mount.
- Ark UI: headless components

# API

- Event handlers / event emitters
Technical documentation is in the [`docs`](./docs/) folder.
27 changes: 27 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Architecture

The UI for the wallet provider selector is made available through a [custom element](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements) Custom elements are easy to integrate in just about any web project. Methods detailed in [Component API](./component-api.md) are provided to interact with the selector.

## Reactivity

The selector uses [Solid](https://www.solidjs.com/) for component logic and reactivity. Solid has a build-time compiler and no runtime, ideal for creating performant and compact shareable components.

## Building the custom element

Solid, like other popular component frameworks, is usually used to manipulate the DOM. To encapsualte the selector's logic into a custom element, the [`solid-element`](https://github.com/solidjs/solid/tree/main/packages/solid-element#readme) package is used.

## Styles

Styles for the selector are in the form of inline styles or `<style>` tags in the source. There doesn't seem to be a better solution for managing styles when building a custom element.

Styling solutions usually have a tight integration with the build process and emit CSS assets or add code that inserts `<style>` tags into the `<header>`. In general, these approaches don't work with custom elements which are defined at run time: there's nothing to inject the styles into during the build process. Dedicated frameworks like Polymer and Stencil exist to overcome this issue, although they currently have fewer Github stars and seem to use more component complex abstractions.

The selector uses a [CSS reset](https://github.com/sindresorhus/modern-normalize/blob/main/modern-normalize.css) that has been [adapted for custom elements](https://www.colorglare.com/css-resets-and-global-styles-in-web-components-c71fcea86dbd).

Styles may break when using Portals. By default, they render elements directly into the body and outside of the influence of the styles defined within the custom element.

The `DM Sans` font used by the selector is added to the document's `<head>` in a `<link>` tag and referenced in the CSS reset. Global fonts can be referenced within custom elements, although importing a font within a custom element [doesn't seem to be working](https://stackoverflow.com/q/78204762/1494725).

## Components

The selector is built with [Ark UI](https://ark-ui.com/) components. They're headless components, meaning they're functional, yet carry no styles and can be styled as needed.
3 changes: 3 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[Architecture](./architecture.md)

[Selector API](./selector-api.md)
32 changes: 32 additions & 0 deletions docs/selector-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Selector API

Interaction with the selector is done by means of custom events. The selector sets up event listeners when it mounts, and convenience methods are available to interact with the selector.

For convenience, all the events are emitted to and listened from `window`.

## Request flow

Below is how third party code would interact with the selector.

```mermaid
sequenceDiagram
Third party code->>Window: Register "select" event handler.
Third party code->>Window: Register "cancel" event handler.
Third party code->>Window: Emit "open" event.<br/>Event payload contains config.<br/>Config contains list of wallet providers.
Window ->> Selector: Trigger "open" handler.
Selector ->> Selector: Process user interaction.
alt User makes selection
Selector->>Window: Emit "select" event.<br/>Event payload contains provider ID.
Window ->>Third party code: Trigger "select" handler.
else Users cancels
Selector->>Window: Emit "cancel" event.
Window ->>Third party code: Trigger "cancel" handler.
end
```

For convenience, the `selectWalletProvider()` method is provided to perform the flow above. It takes care of setting up the necessary event handlers, returns the result as a `Promise` and cleans up the event handlers when done.

## A note on Typescript

The event names used by the selector are defined in [`selector-events.ts`](../src/selector-events.ts), which is necessary to satisfy the compiler.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"solid-element": "1.8.0",
"solid-js": "1.8.15",
"typescript": "5.4.2",
"vite": "5.1.6",
"vite-plugin-dts": "3.7.3",
"vite-plugin-solid": "2.10.1",
"vite": "5.1.6"
"vite-plugin-solid": "2.10.1"
}
}
4 changes: 2 additions & 2 deletions src/ExampleDApp.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { registerElement, selectWalletProvider } from "./lib/utils";
import { loadSelector, selectWalletProvider } from "./lib/utils";
import { mockGetAvailableProviders } from "./mocks";

registerElement();
loadSelector();

function handleButtonClick() {
selectWalletProvider(mockGetAvailableProviders())
Expand Down
6 changes: 4 additions & 2 deletions src/lib/WalletProviderSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ export function WalletProviderSelector() {
window.addEventListener(open, handleOpen);
window.addEventListener(close, handleClose);

// Adds the DM Sans font stylesheet into the document head. Seems fonts
// can't (or are difficult) to load from the shadow DOM, yet globally
// Adds the DM Sans font stylesheet into the document head. Fonts don't seem
// to be picked up when imported from witin the shadow DOM, yet globally
// available fonts are usable within the shadow DOM.
//
// See: https://stackoverflow.com/q/78204762/1494725
document.head.appendChild(
(
<link
Expand Down
5 changes: 3 additions & 2 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ export function getWalletProviderSelectorElement() {
}

/**
* Call this once in your app to register the wallet provider selector element.
* Call this once in your app. It registers the selector's custom element
* definition and adds it to the `<body>`.
*/
export function registerElement() {
export function loadSelector() {
if (customElements.get(elementName)) {
return;
}
Expand Down

0 comments on commit 25cf924

Please sign in to comment.