@hashsome/ui 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/debug.d.ts +27 -0
- package/dist/debug.js +95 -0
- package/dist/entities/artwork-ring.d.ts +4 -2
- package/dist/entities/artwork-ring.js +78 -61
- package/dist/entities/climate-tile.js +2 -1
- package/dist/entities/media-browser.d.ts +3 -1
- package/dist/entities/media-browser.js +41 -15
- package/dist/entities/media-player-bar.js +1 -1
- package/dist/entities/media-player-column.d.ts +5 -2
- package/dist/entities/media-player-column.js +33 -5
- package/dist/entities/media-player-full.d.ts +12 -2
- package/dist/entities/media-player-full.js +45 -3
- package/dist/entities/now-playing.d.ts +3 -1
- package/dist/entities/now-playing.js +8 -3
- package/dist/entities/sensor-readout.js +8 -4
- package/dist/entities/top-bar.js +6 -2
- package/dist/gallery/gallery.js +13 -1
- package/dist/gallery/props-data.js +125 -3
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/layout/animated-number.d.ts +15 -0
- package/dist/layout/animated-number.js +53 -0
- package/dist/layout/board.d.ts +26 -0
- package/dist/layout/board.js +85 -0
- package/dist/layout/debug-menu.d.ts +11 -0
- package/dist/layout/debug-menu.js +99 -0
- package/dist/layout/drawer-controls.js +18 -6
- package/dist/layout/energy-chart.js +2 -3
- package/dist/layout/grid-overlay.d.ts +6 -0
- package/dist/layout/grid-overlay.js +143 -0
- package/dist/layout/page.d.ts +7 -1
- package/dist/layout/page.js +43 -6
- package/dist/layout/room-header.js +1 -1
- package/dist/layout/tile.js +10 -2
- package/dist/layout/top-row.d.ts +9 -0
- package/dist/layout/top-row.js +10 -0
- package/dist/provider.d.ts +7 -3
- package/dist/provider.js +39 -21
- package/dist/theme/grid.d.ts +30 -0
- package/dist/theme/grid.js +38 -0
- package/package.json +2 -2
package/dist/provider.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { RemoteClient } from '@hashsome/core';
|
|
2
|
+
import { RemoteClient, } from '@hashsome/core';
|
|
3
3
|
import { Global, ThemeProvider as EmotionThemeProvider } from '@emotion/react';
|
|
4
4
|
import { ThemeProvider } from 'e-prim';
|
|
5
5
|
import { MotionGlobalConfig } from 'motion/react';
|
|
6
|
-
import { createContext, useContext, useEffect, useMemo, useState, useSyncExternalStore, } from 'react';
|
|
6
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useState, useSyncExternalStore, } from 'react';
|
|
7
|
+
import { DebugContext, debugFromEnv, useDebugState, useFullscreenKept, } from './debug.js';
|
|
8
|
+
import { DebugMenu } from './layout/debug-menu.js';
|
|
7
9
|
import { DetailProvider } from './layout/detail-provider.js';
|
|
8
10
|
import { EntityDrawer } from './layout/entity-drawer.js';
|
|
9
11
|
import { UNITS_PER_SPACE } from './theme/density.js';
|
|
@@ -51,22 +53,13 @@ export function useThemeToggle() {
|
|
|
51
53
|
}
|
|
52
54
|
return value;
|
|
53
55
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
*
|
|
57
|
-
function
|
|
56
|
+
/** What the address asks for motion: `?motion=reduced` or `?motion=full`. Nothing is kept, and the
|
|
57
|
+
* app reads it once, when it starts, so moving between pages inside the app keeps it and a reload
|
|
58
|
+
* without it does not. `?motion=auto` (or anything else) leaves it to the prop. */
|
|
59
|
+
function motionFromAddress() {
|
|
58
60
|
try {
|
|
59
61
|
const asked = new URLSearchParams(window.location.search).get('motion');
|
|
60
|
-
|
|
61
|
-
localStorage.setItem(MOTION_KEY, asked);
|
|
62
|
-
return asked;
|
|
63
|
-
}
|
|
64
|
-
if (asked === 'auto') {
|
|
65
|
-
localStorage.removeItem(MOTION_KEY);
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
const kept = localStorage.getItem(MOTION_KEY);
|
|
69
|
-
return kept === 'reduced' || kept === 'full' ? kept : undefined;
|
|
62
|
+
return asked === 'reduced' || asked === 'full' ? asked : undefined;
|
|
70
63
|
}
|
|
71
64
|
catch {
|
|
72
65
|
return undefined;
|
|
@@ -77,7 +70,7 @@ function motionFromDevice() {
|
|
|
77
70
|
* transitions and animations too. */
|
|
78
71
|
function useMotionMode(preference) {
|
|
79
72
|
const [mode] = useState(() => {
|
|
80
|
-
const chosen =
|
|
73
|
+
const chosen = motionFromAddress() ?? preference;
|
|
81
74
|
MotionGlobalConfig.skipAnimations = chosen === 'reduced';
|
|
82
75
|
if (typeof document !== 'undefined') {
|
|
83
76
|
if (chosen === 'reduced') {
|
|
@@ -102,6 +95,22 @@ function rememberedSun() {
|
|
|
102
95
|
return undefined;
|
|
103
96
|
}
|
|
104
97
|
}
|
|
98
|
+
/** The sun entity a theme follows, if it is a sun schedule. */
|
|
99
|
+
function sunOf(mode) {
|
|
100
|
+
return typeof mode === 'object' && 'sun' in mode ? mode.sun : undefined;
|
|
101
|
+
}
|
|
102
|
+
/** The theme in effect: what the debug menu chose, if it chose, else what the project configured.
|
|
103
|
+
* The sun is the project's: the one its theme follows, or the `sun` it gave. Without one there is no
|
|
104
|
+
* sun to follow, so that choice (kept from a visit that had one) leaves the configured theme as it was. */
|
|
105
|
+
function chosenTheme(configured, choice, sun) {
|
|
106
|
+
if (choice === null) {
|
|
107
|
+
return configured;
|
|
108
|
+
}
|
|
109
|
+
if (choice === 'sun') {
|
|
110
|
+
return sun === undefined ? configured : { sun };
|
|
111
|
+
}
|
|
112
|
+
return choice;
|
|
113
|
+
}
|
|
105
114
|
/** Resolves the configured mode: `'system'` against the live OS/browser preference (updating if it
|
|
106
115
|
* changes while open — a kiosk tablet left running overnight should follow a scheduled OS-level dark
|
|
107
116
|
* mode, for example), a time range against the clock (re-checked at each boundary and whenever the
|
|
@@ -151,7 +160,12 @@ function useThemeMode(mode, client) {
|
|
|
151
160
|
document.removeEventListener('visibilitychange', onVisible);
|
|
152
161
|
};
|
|
153
162
|
}, [from, to]);
|
|
154
|
-
|
|
163
|
+
// Memoized, like every other `subscribe` given to `useSyncExternalStore` (see hooks.ts): a new
|
|
164
|
+
// function each render makes React resubscribe, which against a remote runtime is an unsubscribe and
|
|
165
|
+
// a subscribe whose reply is a new entity object, which renders again, without end.
|
|
166
|
+
const subscribeSun = useCallback((onChange) => (sunRef ? client.subscribe(sunRef, onChange) : () => undefined), [client, sunRef]);
|
|
167
|
+
const readSun = useCallback(() => (sunRef ? client.getEntity(sunRef) : undefined), [client, sunRef]);
|
|
168
|
+
const sun = useSyncExternalStore(subscribeSun, readSun, () => undefined);
|
|
155
169
|
const sunDown = sunIsDown(sun);
|
|
156
170
|
useEffect(() => {
|
|
157
171
|
if (sunDown === undefined) {
|
|
@@ -181,10 +195,14 @@ function useThemeMode(mode, client) {
|
|
|
181
195
|
return { resolved, toggle: () => setOverride(resolved === 'dark' ? 'light' : 'dark') };
|
|
182
196
|
}
|
|
183
197
|
/** Connects the tree to the runtime proxy. Render only on the client. */
|
|
184
|
-
export function HashsomeProvider({ url, client, clientOptions, theme = 'dark', motion = 'auto', font = DEFAULT_FONT, density = 'comfortable', overrides, children, }) {
|
|
198
|
+
export function HashsomeProvider({ url, client, clientOptions, theme = 'dark', motion = 'auto', font = DEFAULT_FONT, density = 'comfortable', overrides, debug = debugFromEnv(), sun, children, }) {
|
|
185
199
|
const instance = useMemo(() => client ?? new RemoteClient({ url: url ?? defaultUrl(), ...clientOptions }), [client, url, clientOptions]);
|
|
186
200
|
useMotionMode(motion);
|
|
187
|
-
const
|
|
201
|
+
const debugState = useDebugState();
|
|
202
|
+
useFullscreenKept(debugState.fullscreen, debugState.setFullscreen);
|
|
203
|
+
// The debug menu can choose a theme over the configured one; the sun is the project's own entity.
|
|
204
|
+
const sunEntity = sunOf(theme) ?? sun;
|
|
205
|
+
const themeMode = useThemeMode(chosenTheme(theme, debug ? debugState.themeChoice : null, sunEntity), instance);
|
|
188
206
|
// Memoized: Emotion recomputes the merged theme (and every `css` prop) when the function changes.
|
|
189
207
|
const withDensity = useMemo(() => (outer) => ({ ...outer, density: densityTokens(density, overrides) }), [density, overrides]);
|
|
190
208
|
useGoogleFont(font);
|
|
@@ -197,7 +215,7 @@ export function HashsomeProvider({ url, client, clientOptions, theme = 'dark', m
|
|
|
197
215
|
instance.connect();
|
|
198
216
|
return () => instance.close();
|
|
199
217
|
}, [instance]);
|
|
200
|
-
return (_jsx(HashsomeContext.Provider, { value: instance, children: _jsx(ThemeModeContext.Provider, { value: themeMode, children: _jsx(ThemeProvider, { theme: resolvedTheme, children: _jsxs(EmotionThemeProvider, { theme: withDensity, children: [_jsx(Global, { styles: globalStyles }), _jsxs(DetailProvider, { children: [children, _jsx(EntityDrawer, {})] })] }) }) }) }));
|
|
218
|
+
return (_jsx(HashsomeContext.Provider, { value: instance, children: _jsx(ThemeModeContext.Provider, { value: themeMode, children: _jsx(ThemeProvider, { theme: resolvedTheme, children: _jsxs(EmotionThemeProvider, { theme: withDensity, children: [_jsx(Global, { styles: globalStyles }), _jsx(DebugContext.Provider, { value: debugState, children: _jsxs(DetailProvider, { children: [children, _jsx(EntityDrawer, {}), debug ? _jsx(DebugMenu, { configured: theme, sun: sunEntity }) : null] }) })] }) }) }) }));
|
|
201
219
|
}
|
|
202
220
|
export function useClient() {
|
|
203
221
|
const client = useContext(HashsomeContext);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** The icon badge a tile starts with, in px: the one size in a tile that does not scale with density. */
|
|
2
|
+
export declare const BADGE = 32;
|
|
3
|
+
/** The height of a room header, in px. */
|
|
4
|
+
export declare const HEADER = 32;
|
|
5
|
+
/** The height of the top row, in px: the clock's line and the tallest things beside it (the switcher, the
|
|
6
|
+
* scene buttons), with no padding of its own, so its content sits as far from the page's edge as
|
|
7
|
+
* everything else does. Sizes that do not scale with density are multiples of 8, so they are whole
|
|
8
|
+
* modules in both (4px and 2.67px). */
|
|
9
|
+
export declare const TOP_ROW = 40;
|
|
10
|
+
/** The vertical grid every card sits on. The module is the theme's spacing unit (a third of a
|
|
11
|
+
* space: 4px in comfortable density, 2.67px in compact), so the gap between cards is always 3
|
|
12
|
+
* modules, and a tile and a header are whole numbers of them in either density. Sizes are in px;
|
|
13
|
+
* `pitch` is a card's height plus the gap that follows it, and a card spanning n pitches is n heights
|
|
14
|
+
* and n-1 gaps tall. */
|
|
15
|
+
export interface GridMetrics {
|
|
16
|
+
module: number;
|
|
17
|
+
gap: number;
|
|
18
|
+
tile: number;
|
|
19
|
+
header: number;
|
|
20
|
+
tilePitch: number;
|
|
21
|
+
headerPitch: number;
|
|
22
|
+
}
|
|
23
|
+
export declare function gridMetrics(space: number): GridMetrics;
|
|
24
|
+
/** Whether a box's top edge, measured from the grid's origin, or its height is off the module grid. Browsers round to the pixel, so a little under a pixel is still on it. */
|
|
25
|
+
export declare function offGrid(top: number, height: number, module: number): boolean;
|
|
26
|
+
/** How a height's leftover (what is under one module after as many whole ones as fit) is shared between the top and the bottom padding: half each, in whole pixels, the odd one to the bottom. `available` is the height inside the page's padding. */
|
|
27
|
+
export declare function centeringOffsets(available: number, module: number): {
|
|
28
|
+
top: number;
|
|
29
|
+
bottom: number;
|
|
30
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { UNITS_PER_SPACE } from './density.js';
|
|
2
|
+
/** The icon badge a tile starts with, in px: the one size in a tile that does not scale with density. */
|
|
3
|
+
export const BADGE = 32;
|
|
4
|
+
/** The height of a room header, in px. */
|
|
5
|
+
export const HEADER = 32;
|
|
6
|
+
/** The height of the top row, in px: the clock's line and the tallest things beside it (the switcher, the
|
|
7
|
+
* scene buttons), with no padding of its own, so its content sits as far from the page's edge as
|
|
8
|
+
* everything else does. Sizes that do not scale with density are multiples of 8, so they are whole
|
|
9
|
+
* modules in both (4px and 2.67px). */
|
|
10
|
+
export const TOP_ROW = 40;
|
|
11
|
+
export function gridMetrics(space) {
|
|
12
|
+
const module = space / UNITS_PER_SPACE;
|
|
13
|
+
// A tile is its badge between two spacing-unit-2 paddings.
|
|
14
|
+
const tile = BADGE + 4 * module;
|
|
15
|
+
return {
|
|
16
|
+
module,
|
|
17
|
+
gap: space,
|
|
18
|
+
tile,
|
|
19
|
+
header: HEADER,
|
|
20
|
+
tilePitch: tile + space,
|
|
21
|
+
headerPitch: HEADER + space,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Whether a box's top edge, measured from the grid's origin, or its height is off the module grid. Browsers round to the pixel, so a little under a pixel is still on it. */
|
|
25
|
+
export function offGrid(top, height, module) {
|
|
26
|
+
const tolerance = Math.max(0.5, module * 0.15);
|
|
27
|
+
const off = (value) => {
|
|
28
|
+
const rest = Math.abs(value) % module;
|
|
29
|
+
return Math.min(rest, module - rest) > tolerance;
|
|
30
|
+
};
|
|
31
|
+
return off(top) || off(height);
|
|
32
|
+
}
|
|
33
|
+
/** How a height's leftover (what is under one module after as many whole ones as fit) is shared between the top and the bottom padding: half each, in whole pixels, the odd one to the bottom. `available` is the height inside the page's padding. */
|
|
34
|
+
export function centeringOffsets(available, module) {
|
|
35
|
+
const leftover = available > 0 ? Math.floor(available % module) : 0;
|
|
36
|
+
const top = Math.floor(leftover / 2);
|
|
37
|
+
return { top, bottom: leftover - top };
|
|
38
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hashsome/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Design system components and hooks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@emotion/react": "11.14.0",
|
|
19
|
-
"@hashsome/core": "0.
|
|
19
|
+
"@hashsome/core": "0.7.0",
|
|
20
20
|
"e-prim": "2.0.1",
|
|
21
21
|
"motion": "13.4.5",
|
|
22
22
|
"react": "19.3.0",
|