Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .DS_Store
Binary file not shown.
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dist/

*.html
*.js
package-lock.json
5 changes: 5 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"trailingComma": "all",
"useTabs": true,
"tabWidth": 4
}
59 changes: 49 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,78 @@ A UI component set for the web.

## For developers

Wireframe is delivered as three files:
Wireframe exists as three files:

- A stylesheet containing all css components
- A javascript module exporting all web components
- A json file with template elements for SSR (strings)

All components are RTL (right-to-left) compatible.

## For designers

`Wireframe` is named after the technique of drafting low-fidelity interfaces with pen and paper.
It's also a reference to the polygon meshes found in graphics programming.

`Wireframe` is an aesthetically concise UI language. Components must differentiate themselves
from text and each other. Their functionality is telegraphed by their visual accent.

I believe creature comforts like expanding circles are ultimately designed for other designers
(and consequentially job security). They do nothing of signifigance for the user.

Consider `wireframe` a love letter to my former colleagues at Material Design.

## CSS components

A single css stylesheet includes the following components:

- button
- primary button
- destructive button
- checkbox
- meter
- number inputs
- number
- phone
- time
- date
- datetime-local
- number
- phone
- time
- date
- datetime-local
- progress
- radio
- slider
- switch
- textarea
- text inputs
- text
- password
- email
- url
- text
- password
- email
- url

- :focus ring

Components not immediately defined by elements are defined by there role:

```css
input[type=checkbox][role=switch]
[tabindex=0]
```

Components not defined by an element or a role are defined by a `data-wf` attribute.

```html
<li data-wf="icon-button"></li>
```

Lastly outline is a special case.

```html
<li data-wf-outline></li>
```

## Web components

The following web components are available:

- inline movement

## License
Expand Down
12 changes: 6 additions & 6 deletions bundle/src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@

import * as fs from "fs";
import * as path from "path";
import { bundle } from 'lightningcss';
import { bundle } from "lightningcss";

let originCssPathInput = process.argv[2];
let targetCssPathInput = process.argv[3];

let cwd = process.cwd();

// odd way of handling this ../../../ for two dirs / and a file
let originCssPath = path.join(cwd, originCssPathInput);
let filename = path.join(cwd, originCssPathInput);
let targetCssPath = path.join(cwd, targetCssPathInput);

let { code, map } = bundle({
filename: originCssPath,
let { code } = bundle({
filename,
});

try {
fs.writeFileSync(targetCssPath, code);
fs.writeFileSync(targetCssPath, code);
} catch (err) {
console.error(err);
console.error(err);
}
1 change: 1 addition & 0 deletions bundle/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["node"],
"rootDir": "./src",
"outDir": "./dist"
}
Expand Down
36 changes: 36 additions & 0 deletions components/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## Component ideas

- inline movement
- block movement
- password
- 2d pad
- Radial pad (2d but circle)

## Aria patterns

- toolbar (inline movement might be built already)
- breadcrumb
- combobox
- tabs
- grid
- listbox

Stuff that might need a webcomponent

- chip / on off button
- tabs
- radio like selection
- could represent a tabs
- could use a form value maybe?
- slider

Designs that fucking suck:

Cards
Navigation
Drop down menues
Anything modal

It's boring to not include them but it's more honest. More effective to use Gestalt.
No amount of dividers is gonna "declutter" the amount of shit in a UI, it just boxes
up a ton of shit.
1 change: 1 addition & 0 deletions components/src/component_templates.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
75 changes: 43 additions & 32 deletions components/src/inline_movement.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/Guides/Keyboard-navigable_JavaScript_widgets#using_tabindex

// correlates to start and end

// get bounding rectangle of first and last child
// determine directionality

export const shadowDom = `<slot></slot>`;
export const template = `<template>
${shadowDom}
<template>`;
// https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/

let templateEl = document.createElement("template");
templateEl.setHTMLUnsafe(shadowDom);
templateEl.setHTMLUnsafe("<slot></slot>");

// You don't need a DSD template because interactivity needs JS.
// So a slot will load when the component will load.
//

export class InlineMovement extends HTMLElement {
#slot = getSlotElement(this);
#mapped = new WeakSet<EventTarget>(this.#slot?.assignedElements() ?? []);
#boundOnSlotChange = this.#onSlotChange.bind(this);
#boundOnClick = this.#onClick.bind(this);
#boundOnKey = this.#onKey.bind(this);
#computedStyle = window.getComputedStyle(this);
#slot = getSlotElement(this);
#mapped = new WeakSet<EventTarget>(this.#slot?.assignedElements() ?? []);

connectedCallback() {
this.#slot?.addEventListener("slotchange", this.#boundOnSlotChange);
Expand All @@ -39,10 +33,18 @@ export class InlineMovement extends HTMLElement {

#onKey(event: KeyboardEvent) {
if (event.defaultPrevented) return;
if (event.shiftKey) return;
if (event.shiftKey || event.altKey) return;

if (handleBigJumps(event, this.#slot)) return;
if (handleArrows(event, this.#slot, this.#mapped, this.#computedStyle)) return;
if (
handleArrows(
event,
this.#slot,
this.#mapped,
window.getComputedStyle(this),
)
)
return;
}

#onClick(event: PointerEvent) {
Expand All @@ -58,19 +60,13 @@ export class InlineMovement extends HTMLElement {
setNegativeTabIndices(this.#slot);
focusOnElement(node);
return;
};
}
}
}

function getSlotElement(el: HTMLElement): HTMLSlotElement | null {
let internals = el.attachInternals();
let ssr = null !== internals.shadowRoot;
let shadowRoot = internals.shadowRoot
? internals.shadowRoot
: el.attachShadow({ mode: "closed" });

if (!ssr)
shadowRoot.appendChild(document.importNode(templateEl.content, true));
let shadowRoot = el.attachShadow({ mode: "closed" });
shadowRoot.appendChild(templateEl.content.cloneNode(true));

return shadowRoot.querySelector("slot");
}
Expand All @@ -88,20 +84,28 @@ function focusOnElement(sibling: HTMLElement) {
sibling.focus();
}

function handleBigJumps(event: KeyboardEvent, slot: HTMLSlotElement | null): boolean {
function handleBigJumps(
event: KeyboardEvent,
slot: HTMLSlotElement | null,
): boolean {
if ("Home" !== event.key && "End" !== event.key) return false;

let bigJump = getFirstOrLast(event, slot);
if (bigJump instanceof HTMLElement) {
event.preventDefault();
setNegativeTabIndices(slot);
focusOnElement(bigJump);
event.preventDefault();
setNegativeTabIndices(slot);
focusOnElement(bigJump);
}

return true;
}

function handleArrows(event: KeyboardEvent, slot: HTMLSlotElement | null, mapped: WeakSet<EventTarget>, computedStyle: CSSStyleDeclaration) {
function handleArrows(
event: KeyboardEvent,
slot: HTMLSlotElement | null,
mapped: WeakSet<EventTarget>,
computedStyle: CSSStyleDeclaration,
) {
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return false;

for (let node of event.composedPath()) {
Expand All @@ -120,15 +124,22 @@ function handleArrows(event: KeyboardEvent, slot: HTMLSlotElement | null, mapped
return true;
}

function getFirstOrLast(event: KeyboardEvent, slot: HTMLSlotElement | null): Element | undefined {
function getFirstOrLast(
event: KeyboardEvent,
slot: HTMLSlotElement | null,
): Element | undefined {
if (!slot) return;

let elements = slot.assignedElements();
if ("Home" === event.key) return elements[0];
if ("End" === event.key) return elements[elements.length - 1];
}

function getSibling(event: KeyboardEvent, node: EventTarget, computedStyle: CSSStyleDeclaration): Element | null | undefined {
function getSibling(
event: KeyboardEvent,
node: EventTarget,
computedStyle: CSSStyleDeclaration,
): Element | null | undefined {
if (!(node instanceof HTMLElement)) return;

let prev = node.previousElementSibling;
Expand Down
2 changes: 1 addition & 1 deletion components/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
"rootDir": "./src",
"outDir": "./dist"
}
}
}
Empty file added demo/baseline.html
Empty file.
Loading