@hashsome/ui 0.1.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.
Files changed (192) hide show
  1. package/LICENSE +674 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/entities/action-button.d.ts +17 -0
  4. package/dist/entities/action-button.js +56 -0
  5. package/dist/entities/action-button.js.map +1 -0
  6. package/dist/entities/artwork-ring.d.ts +17 -0
  7. package/dist/entities/artwork-ring.js +89 -0
  8. package/dist/entities/artwork-ring.js.map +1 -0
  9. package/dist/entities/climate-tile.d.ts +20 -0
  10. package/dist/entities/climate-tile.js +98 -0
  11. package/dist/entities/climate-tile.js.map +1 -0
  12. package/dist/entities/light-color.d.ts +52 -0
  13. package/dist/entities/light-color.js +148 -0
  14. package/dist/entities/light-color.js.map +1 -0
  15. package/dist/entities/light-tile.d.ts +24 -0
  16. package/dist/entities/light-tile.js +139 -0
  17. package/dist/entities/light-tile.js.map +1 -0
  18. package/dist/entities/media-browser.d.ts +15 -0
  19. package/dist/entities/media-browser.js +67 -0
  20. package/dist/entities/media-browser.js.map +1 -0
  21. package/dist/entities/media-controls.d.ts +20 -0
  22. package/dist/entities/media-controls.js +53 -0
  23. package/dist/entities/media-controls.js.map +1 -0
  24. package/dist/entities/media-player-bar.d.ts +14 -0
  25. package/dist/entities/media-player-bar.js +78 -0
  26. package/dist/entities/media-player-bar.js.map +1 -0
  27. package/dist/entities/media-player-body.d.ts +16 -0
  28. package/dist/entities/media-player-body.js +18 -0
  29. package/dist/entities/media-player-body.js.map +1 -0
  30. package/dist/entities/media-player-column.d.ts +18 -0
  31. package/dist/entities/media-player-column.js +34 -0
  32. package/dist/entities/media-player-column.js.map +1 -0
  33. package/dist/entities/media-player-page.d.ts +12 -0
  34. package/dist/entities/media-player-page.js +13 -0
  35. package/dist/entities/media-player-page.js.map +1 -0
  36. package/dist/entities/media-progress.d.ts +7 -0
  37. package/dist/entities/media-progress.js +30 -0
  38. package/dist/entities/media-progress.js.map +1 -0
  39. package/dist/entities/media-seek.d.ts +9 -0
  40. package/dist/entities/media-seek.js +19 -0
  41. package/dist/entities/media-seek.js.map +1 -0
  42. package/dist/entities/nav-dock.d.ts +13 -0
  43. package/dist/entities/nav-dock.js +22 -0
  44. package/dist/entities/nav-dock.js.map +1 -0
  45. package/dist/entities/nav-rail.d.ts +19 -0
  46. package/dist/entities/nav-rail.js +18 -0
  47. package/dist/entities/nav-rail.js.map +1 -0
  48. package/dist/entities/now-playing.d.ts +20 -0
  49. package/dist/entities/now-playing.js +34 -0
  50. package/dist/entities/now-playing.js.map +1 -0
  51. package/dist/entities/scene-button.d.ts +13 -0
  52. package/dist/entities/scene-button.js +6 -0
  53. package/dist/entities/scene-button.js.map +1 -0
  54. package/dist/entities/seek-line.d.ts +9 -0
  55. package/dist/entities/seek-line.js +71 -0
  56. package/dist/entities/seek-line.js.map +1 -0
  57. package/dist/entities/sensor-readout.d.ts +28 -0
  58. package/dist/entities/sensor-readout.js +95 -0
  59. package/dist/entities/sensor-readout.js.map +1 -0
  60. package/dist/entities/top-bar.d.ts +68 -0
  61. package/dist/entities/top-bar.js +248 -0
  62. package/dist/entities/top-bar.js.map +1 -0
  63. package/dist/entity-handle.d.ts +21 -0
  64. package/dist/entity-handle.js +16 -0
  65. package/dist/entity-handle.js.map +1 -0
  66. package/dist/gallery/fixtures.d.ts +4 -0
  67. package/dist/gallery/fixtures.js +119 -0
  68. package/dist/gallery/fixtures.js.map +1 -0
  69. package/dist/gallery/gallery.d.ts +9 -0
  70. package/dist/gallery/gallery.js +355 -0
  71. package/dist/gallery/gallery.js.map +1 -0
  72. package/dist/gallery/index.d.ts +2 -0
  73. package/dist/gallery/index.js +2 -0
  74. package/dist/gallery/index.js.map +1 -0
  75. package/dist/gallery/props-data.d.ts +10 -0
  76. package/dist/gallery/props-data.js +718 -0
  77. package/dist/gallery/props-data.js.map +1 -0
  78. package/dist/hooks.d.ts +19 -0
  79. package/dist/hooks.js +53 -0
  80. package/dist/hooks.js.map +1 -0
  81. package/dist/icon-data.d.ts +6 -0
  82. package/dist/icon-data.js +59193 -0
  83. package/dist/icon-data.js.map +1 -0
  84. package/dist/icon.d.ts +9 -0
  85. package/dist/icon.js +28 -0
  86. package/dist/icon.js.map +1 -0
  87. package/dist/index.d.ts +30 -0
  88. package/dist/index.js +24 -0
  89. package/dist/index.js.map +1 -0
  90. package/dist/layout/cover.d.ts +5 -0
  91. package/dist/layout/cover.js +8 -0
  92. package/dist/layout/cover.js.map +1 -0
  93. package/dist/layout/date-time-field.d.ts +15 -0
  94. package/dist/layout/date-time-field.js +108 -0
  95. package/dist/layout/date-time-field.js.map +1 -0
  96. package/dist/layout/detail-provider.d.ts +22 -0
  97. package/dist/layout/detail-provider.js +26 -0
  98. package/dist/layout/detail-provider.js.map +1 -0
  99. package/dist/layout/drawer-controls.d.ts +46 -0
  100. package/dist/layout/drawer-controls.js +93 -0
  101. package/dist/layout/drawer-controls.js.map +1 -0
  102. package/dist/layout/energy-chart.d.ts +37 -0
  103. package/dist/layout/energy-chart.js +85 -0
  104. package/dist/layout/energy-chart.js.map +1 -0
  105. package/dist/layout/energy-usage.d.ts +6 -0
  106. package/dist/layout/energy-usage.js +18 -0
  107. package/dist/layout/energy-usage.js.map +1 -0
  108. package/dist/layout/entity-drawer.d.ts +15 -0
  109. package/dist/layout/entity-drawer.js +77 -0
  110. package/dist/layout/entity-drawer.js.map +1 -0
  111. package/dist/layout/fade-scroll.d.ts +14 -0
  112. package/dist/layout/fade-scroll.js +59 -0
  113. package/dist/layout/fade-scroll.js.map +1 -0
  114. package/dist/layout/grid.d.ts +9 -0
  115. package/dist/layout/grid.js +6 -0
  116. package/dist/layout/grid.js.map +1 -0
  117. package/dist/layout/history-section.d.ts +19 -0
  118. package/dist/layout/history-section.js +39 -0
  119. package/dist/layout/history-section.js.map +1 -0
  120. package/dist/layout/hold-progress.d.ts +2 -0
  121. package/dist/layout/hold-progress.js +9 -0
  122. package/dist/layout/hold-progress.js.map +1 -0
  123. package/dist/layout/icon-button-styles.d.ts +9 -0
  124. package/dist/layout/icon-button-styles.js +28 -0
  125. package/dist/layout/icon-button-styles.js.map +1 -0
  126. package/dist/layout/page.d.ts +17 -0
  127. package/dist/layout/page.js +29 -0
  128. package/dist/layout/page.js.map +1 -0
  129. package/dist/layout/plain-button.d.ts +11 -0
  130. package/dist/layout/plain-button.js +17 -0
  131. package/dist/layout/plain-button.js.map +1 -0
  132. package/dist/layout/room-header.d.ts +12 -0
  133. package/dist/layout/room-header.js +11 -0
  134. package/dist/layout/room-header.js.map +1 -0
  135. package/dist/layout/round-button.d.ts +13 -0
  136. package/dist/layout/round-button.js +19 -0
  137. package/dist/layout/round-button.js.map +1 -0
  138. package/dist/layout/sensor-history.d.ts +15 -0
  139. package/dist/layout/sensor-history.js +91 -0
  140. package/dist/layout/sensor-history.js.map +1 -0
  141. package/dist/layout/sensor-stats.d.ts +21 -0
  142. package/dist/layout/sensor-stats.js +79 -0
  143. package/dist/layout/sensor-stats.js.map +1 -0
  144. package/dist/layout/series-chart.d.ts +25 -0
  145. package/dist/layout/series-chart.js +46 -0
  146. package/dist/layout/series-chart.js.map +1 -0
  147. package/dist/layout/stepper.d.ts +10 -0
  148. package/dist/layout/stepper.js +12 -0
  149. package/dist/layout/stepper.js.map +1 -0
  150. package/dist/layout/swatch-row.d.ts +19 -0
  151. package/dist/layout/swatch-row.js +20 -0
  152. package/dist/layout/swatch-row.js.map +1 -0
  153. package/dist/layout/tile.d.ts +67 -0
  154. package/dist/layout/tile.js +207 -0
  155. package/dist/layout/tile.js.map +1 -0
  156. package/dist/layout/use-drawer.d.ts +33 -0
  157. package/dist/layout/use-drawer.js +34 -0
  158. package/dist/layout/use-drawer.js.map +1 -0
  159. package/dist/layout/use-hold.d.ts +21 -0
  160. package/dist/layout/use-hold.js +61 -0
  161. package/dist/layout/use-hold.js.map +1 -0
  162. package/dist/provider.d.ts +37 -0
  163. package/dist/provider.js +95 -0
  164. package/dist/provider.js.map +1 -0
  165. package/dist/status.d.ts +8 -0
  166. package/dist/status.js +27 -0
  167. package/dist/status.js.map +1 -0
  168. package/dist/theme/density.d.ts +16 -0
  169. package/dist/theme/density.js +7 -0
  170. package/dist/theme/density.js.map +1 -0
  171. package/dist/theme/global-styles.d.ts +5 -0
  172. package/dist/theme/global-styles.js +44 -0
  173. package/dist/theme/global-styles.js.map +1 -0
  174. package/dist/theme/index.d.ts +19 -0
  175. package/dist/theme/index.js +106 -0
  176. package/dist/theme/index.js.map +1 -0
  177. package/dist/theme/overrides.d.ts +28 -0
  178. package/dist/theme/overrides.js +24 -0
  179. package/dist/theme/overrides.js.map +1 -0
  180. package/dist/theme/tokens.d.ts +78 -0
  181. package/dist/theme/tokens.js +1 -0
  182. package/dist/theme/tokens.js.map +1 -0
  183. package/dist/theme/use-typography.d.ts +4 -0
  184. package/dist/theme/use-typography.js +9 -0
  185. package/dist/theme/use-typography.js.map +1 -0
  186. package/dist/use-entity-history.d.ts +12 -0
  187. package/dist/use-entity-history.js +43 -0
  188. package/dist/use-entity-history.js.map +1 -0
  189. package/dist/use-media-browser.d.ts +31 -0
  190. package/dist/use-media-browser.js +87 -0
  191. package/dist/use-media-browser.js.map +1 -0
  192. package/package.json +57 -0
@@ -0,0 +1,5 @@
1
+ import type { Interpolation, Theme } from '@emotion/react';
2
+ /** The app-wide styles `HashsomeProvider` installs once: the page background/text/font, native
3
+ * controls following the theme, border-box sizing, and a low-specificity `button`/`input` reset (so a component's
4
+ * own class always wins over it). Global because the theme is an app property, not a per-page one. */
5
+ export declare function globalStyles({ palette, typography }: Theme): Interpolation<Theme>;
@@ -0,0 +1,44 @@
1
+ /** The scrollbar's width (its track) and how much of that is the visible thumb. */
2
+ const SCROLLBAR = 14;
3
+ const THUMB = 4;
4
+ function isDark(hex) {
5
+ const n = parseInt(hex.replace('#', '').slice(0, 6), 16);
6
+ return Number.isFinite(n) && ((n >> 16) & 255) + ((n >> 8) & 255) + (n & 255) < 384;
7
+ }
8
+ /** The app-wide styles `HashsomeProvider` installs once: the page background/text/font, native
9
+ * controls following the theme, border-box sizing, and a low-specificity `button`/`input` reset (so a component's
10
+ * own class always wins over it). Global because the theme is an app property, not a per-page one. */
11
+ export function globalStyles({ palette, typography }) {
12
+ return {
13
+ ':root': {
14
+ // Native controls (date pickers' calendar glyph, scrollbars) follow the theme, not the OS.
15
+ colorScheme: isDark(palette.bg) ? 'dark' : 'light',
16
+ },
17
+ '*, *::before, *::after': { boxSizing: 'border-box' },
18
+ body: {
19
+ background: palette.bg,
20
+ color: palette.text,
21
+ fontFamily: typography.default.fontFamily,
22
+ },
23
+ button: { font: 'inherit', color: 'inherit' },
24
+ // Buttons and inputs start borderless; one that wants a border asks for it with the `border` prop.
25
+ 'button, input': { border: 0 },
26
+ // Scrollbars: thin, no track, in the theme's colors. The track is wider than the thumb and the
27
+ // thumb has a transparent border around it, so there is air between the bar and the content.
28
+ // (Chrome and Safari take the `::-webkit-scrollbar` rules; Firefox, which has no equivalent for
29
+ // the gap, gets a thin bar from the standard properties. Setting those in Chrome would turn the
30
+ // webkit rules off, hence the `@supports`.)
31
+ '::-webkit-scrollbar': { width: SCROLLBAR, height: SCROLLBAR },
32
+ '::-webkit-scrollbar-track, ::-webkit-scrollbar-corner': { background: 'transparent' },
33
+ '::-webkit-scrollbar-thumb': {
34
+ background: palette.border,
35
+ backgroundClip: 'padding-box',
36
+ border: `${(SCROLLBAR - THUMB) / 2}px solid transparent`,
37
+ borderRadius: 999,
38
+ },
39
+ '::-webkit-scrollbar-thumb:hover': { background: palette.line, backgroundClip: 'padding-box' },
40
+ '@supports not selector(::-webkit-scrollbar)': {
41
+ '*': { scrollbarWidth: 'thin', scrollbarColor: `${palette.border} transparent` },
42
+ },
43
+ };
44
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global-styles.js","sourceRoot":"","sources":["../../src/theme/global-styles.ts"],"names":[],"mappings":"AAEA,mFAAmF;AACnF,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,KAAK,GAAG,CAAC,CAAC;AAEhB,SAAS,MAAM,CAAC,GAAW;IACzB,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzD,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AACtF,CAAC;AAED;;sGAEsG;AACtG,MAAM,UAAU,YAAY,CAAC,EAAE,OAAO,EAAE,UAAU,EAAS;IACzD,OAAO;QACL,OAAO,EAAE;YACP,2FAA2F;YAC3F,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;SACnD;QACD,wBAAwB,EAAE,EAAE,SAAS,EAAE,YAAY,EAAE;QACrD,IAAI,EAAE;YACJ,UAAU,EAAE,OAAO,CAAC,EAAE;YACtB,KAAK,EAAE,OAAO,CAAC,IAAI;YACnB,UAAU,EAAE,UAAU,CAAC,OAAO,CAAC,UAAU;SAC1C;QACD,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;QAC7C,mGAAmG;QACnG,eAAe,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE;QAC9B,+FAA+F;QAC/F,6FAA6F;QAC7F,gGAAgG;QAChG,gGAAgG;QAChG,4CAA4C;QAC5C,qBAAqB,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE;QAC9D,uDAAuD,EAAE,EAAE,UAAU,EAAE,aAAa,EAAE;QACtF,2BAA2B,EAAE;YAC3B,UAAU,EAAE,OAAO,CAAC,MAAM;YAC1B,cAAc,EAAE,aAAa;YAC7B,MAAM,EAAE,GAAG,CAAC,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,sBAAsB;YACxD,YAAY,EAAE,GAAG;SAClB;QACD,iCAAiC,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,IAAI,EAAE,cAAc,EAAE,aAAa,EAAE;QAC9F,6CAA6C,EAAE;YAC7C,GAAG,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,MAAM,cAAc,EAAE;SACjF;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { ThemeConfig } from 'e-prim';
2
+ import './tokens.ts';
3
+ /** The Google Fonts family loaded and applied by default — see `HashsomeProviderProps.font` . Any other Google Fonts family name works the same way. */
4
+ export declare const DEFAULT_FONT = "Inter";
5
+ /** Builds a CSS `font-family` value for a chosen font, falling back to the system stack if it
6
+ * hasn't loaded yet (or fails to). */
7
+ export declare function fontStack(family: string): string;
8
+ /** The Google Fonts CSS endpoint for a family name, covering the weights `@hashsome/ui` actually uses
9
+ * (400/500/600/700). Spaces become `+` per Google Fonts' own URL convention. */
10
+ export declare function googleFontHref(family: string): string;
11
+ /** Returns `theme` with every `Typography` variant's font swapped to `family` (falling back to
12
+ * the system stack) — every variant inherits `typography.default`, so this is the one place that
13
+ * needs to change. Used by `HashsomeProvider` to apply `HashsomeProviderProps.font` at runtime, since the
14
+ * `darkTheme`/`lightTheme` exports below are built once with the compile-time default. */
15
+ export declare function withFontFamily(theme: ThemeConfig, family: string): ThemeConfig;
16
+ /** The converged "Ember" dark palette (default; the only theme currently wired up). */
17
+ export declare const darkTheme: ThemeConfig;
18
+ /** Ember's light counterpart, selected via `<HashsomeProvider theme="light">` (or `"system"`). */
19
+ export declare const lightTheme: ThemeConfig;
@@ -0,0 +1,106 @@
1
+ import './tokens.js';
2
+ /** The Google Fonts family loaded and applied by default — see `HashsomeProviderProps.font` . Any other Google Fonts family name works the same way. */
3
+ export const DEFAULT_FONT = 'Inter';
4
+ const FALLBACK_FONTS = "system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif";
5
+ /** Builds a CSS `font-family` value for a chosen font, falling back to the system stack if it
6
+ * hasn't loaded yet (or fails to). */
7
+ export function fontStack(family) {
8
+ return `'${family}', ${FALLBACK_FONTS}`;
9
+ }
10
+ /** The Google Fonts CSS endpoint for a family name, covering the weights `@hashsome/ui` actually uses
11
+ * (400/500/600/700). Spaces become `+` per Google Fonts' own URL convention. */
12
+ export function googleFontHref(family) {
13
+ return `https://fonts.googleapis.com/css2?family=${family.trim().replace(/\s+/g, '+')}:wght@400;500;600;700&display=swap`;
14
+ }
15
+ /** Returns `theme` with every `Typography` variant's font swapped to `family` (falling back to
16
+ * the system stack) — every variant inherits `typography.default`, so this is the one place that
17
+ * needs to change. Used by `HashsomeProvider` to apply `HashsomeProviderProps.font` at runtime, since the
18
+ * `darkTheme`/`lightTheme` exports below are built once with the compile-time default. */
19
+ export function withFontFamily(theme, family) {
20
+ return {
21
+ ...theme,
22
+ typography: {
23
+ ...theme.typography,
24
+ default: { ...theme.typography.default, fontFamily: fontStack(family) },
25
+ },
26
+ };
27
+ }
28
+ const FONT_FAMILY = fontStack(DEFAULT_FONT);
29
+ const shared = {
30
+ spacing: 4,
31
+ breakpoint: {},
32
+ zIndex: {
33
+ nav: 5,
34
+ dropdown: 10,
35
+ scrim: 20,
36
+ drawer: 21,
37
+ popover: 30,
38
+ },
39
+ shadow: {
40
+ drawer: '0 20px 48px rgba(0, 0, 0, 0.5)',
41
+ },
42
+ radius: {
43
+ full: '999px',
44
+ card: '25px',
45
+ drawer: '24px',
46
+ chrome: '14px',
47
+ row: '16px',
48
+ small: '9px',
49
+ },
50
+ border: { color: 'border', width: 1 },
51
+ typography: {
52
+ default: { fontFamily: FONT_FAMILY, fontSize: 16 },
53
+ label: { fontSize: 12, fontWeight: 500 },
54
+ secondary: { fontSize: 11 },
55
+ heading: { fontSize: 14, fontWeight: 600 },
56
+ body: { fontSize: 13, fontWeight: 400 },
57
+ bodyStrong: { fontSize: 13, fontWeight: 600 },
58
+ eyebrow: { fontSize: 11, fontWeight: 600, letterSpacing: '0.04em' },
59
+ stat: { fontSize: 20, fontWeight: 600 },
60
+ title: { fontSize: 16, fontWeight: 400 },
61
+ roomTitle: { fontSize: 14, fontWeight: 400 },
62
+ clock: { fontSize: 32, fontWeight: 400 },
63
+ },
64
+ };
65
+ // `ThemeProvider` calls e-prim's own `makeTheme()` internally on whatever it's given — passing
66
+ // an already-`makeTheme()`-processed object here would run it twice, which silently corrupts the
67
+ // derived `spacing()` function (every `gap`/`p`/`m`/`mb` prop then resolves to `NaN`). These stay
68
+ // as plain `ThemeConfig` objects; `<ThemeProvider theme={darkTheme}>` does the one-time build.
69
+ /** The converged "Ember" dark palette (default; the only theme currently wired up). */
70
+ export const darkTheme = {
71
+ ...shared,
72
+ palette: {
73
+ bg: '#0B0A0D',
74
+ surface: '#17151A',
75
+ surfaceRaised: '#211E26',
76
+ text: '#F2EFEA',
77
+ textMuted: '#96908C',
78
+ accent: '#FF7A45',
79
+ accentText: '#FFFFFF',
80
+ onAccent: '#1B1B1F',
81
+ warm: '#E3B341',
82
+ danger: '#F2554A',
83
+ line: '#726C6A',
84
+ rail: '#141216',
85
+ border: 'rgba(255, 255, 255, 0.09)',
86
+ },
87
+ };
88
+ /** Ember's light counterpart, selected via `<HashsomeProvider theme="light">` (or `"system"`). */
89
+ export const lightTheme = {
90
+ ...shared,
91
+ palette: {
92
+ bg: '#F4F1EB',
93
+ surface: '#FFFFFF',
94
+ surfaceRaised: '#EFEAE1',
95
+ text: '#211E26',
96
+ textMuted: '#8A8480',
97
+ accent: '#FF7A45',
98
+ accentText: '#FFFFFF',
99
+ onAccent: '#1B1B1F',
100
+ warm: '#E3B341',
101
+ danger: '#F2554A',
102
+ line: '#4A4540',
103
+ rail: '#EAE4DA',
104
+ border: 'rgba(0, 0, 0, 0.08)',
105
+ },
106
+ };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AACA,OAAO,aAAa,CAAC;AAErB,wJAAwJ;AACxJ,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC;AAEpC,MAAM,cAAc,GAAG,0DAA0D,CAAC;AAElF;sCACsC;AACtC,MAAM,UAAU,SAAS,CAAC,MAAc;IACtC,OAAO,IAAI,MAAM,MAAM,cAAc,EAAE,CAAC;AAC1C,CAAC;AAED;gFACgF;AAChF,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,OAAO,4CAA4C,MAAM,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,oCAAoC,CAAC;AAC5H,CAAC;AAED;;;0FAG0F;AAC1F,MAAM,UAAU,cAAc,CAAC,KAAkB,EAAE,MAAc;IAC/D,OAAO;QACL,GAAG,KAAK;QACR,UAAU,EAAE;YACV,GAAG,KAAK,CAAC,UAAU;YACnB,OAAO,EAAE,EAAE,GAAG,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE;SACxE;KACF,CAAC;AACJ,CAAC;AAED,MAAM,WAAW,GAAG,SAAS,CAAC,YAAY,CAAC,CAAC;AAE5C,MAAM,MAAM,GAAG;IACb,OAAO,EAAE,CAAC;IACV,UAAU,EAAE,EAAE;IACd,MAAM,EAAE;QACN,GAAG,EAAE,CAAC;QACN,QAAQ,EAAE,EAAE;QACZ,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,EAAE;QACV,OAAO,EAAE,EAAE;KACZ;IACD,MAAM,EAAE;QACN,MAAM,EAAE,gCAAgC;KACzC;IACD,MAAM,EAAE;QACN,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,MAAM;QACZ,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,MAAM;QACd,GAAG,EAAE,MAAM;QACX,KAAK,EAAE,KAAK;KACb;IACD,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE;IACrC,UAAU,EAAE;QACV,OAAO,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,EAAE;QAClD,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE;QACxC,SAAS,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;QAC3B,OAAO,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE;QAC1C,IAAI,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE;QACvC,UAAU,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE;QAC7C,OAAO,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE;QACnE,IAAI,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE;QACvC,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE;QACxC,SAAS,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE;QAC5C,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE;KACzC;CACqC,CAAC;AAEzC,+FAA+F;AAC/F,iGAAiG;AACjG,kGAAkG;AAClG,+FAA+F;AAE/F,uFAAuF;AACvF,MAAM,CAAC,MAAM,SAAS,GAAgB;IACpC,GAAG,MAAM;IACT,OAAO,EAAE;QACP,EAAE,EAAE,SAAS;QACb,OAAO,EAAE,SAAS;QAClB,aAAa,EAAE,SAAS;QACxB,IAAI,EAAE,SAAS;QACf,SAAS,EAAE,SAAS;QACpB,MAAM,EAAE,SAAS;QACjB,UAAU,EAAE,SAAS;QACrB,QAAQ,EAAE,SAAS;QACnB,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,SAAS;QACjB,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,2BAA2B;KACpC;CACF,CAAC;AAEF,kGAAkG;AAClG,MAAM,CAAC,MAAM,UAAU,GAAgB;IACrC,GAAG,MAAM;IACT,OAAO,EAAE;QACP,EAAE,EAAE,SAAS;QACb,OAAO,EAAE,SAAS;QAClB,aAAa,EAAE,SAAS;QACxB,IAAI,EAAE,SAAS;QACf,SAAS,EAAE,SAAS;QACpB,MAAM,EAAE,SAAS;QACjB,UAAU,EAAE,SAAS;QACrB,QAAQ,EAAE,SAAS;QACnB,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,SAAS;QACjB,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,qBAAqB;KAC9B;CACF,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { ThemeConfig, TypographyKey, TypographySpecs } from 'e-prim';
2
+ import { type Density, type DensityTokens } from './density.ts';
3
+ /** Changes to the built-in theme. Everything is optional and partial: name only what differs and
4
+ * the rest keeps its default. Only existing tokens can be changed. */
5
+ export interface ThemeOverrides {
6
+ /** Colors for the light theme (`theme="light"`, or `"system"` while the OS is light). */
7
+ light?: {
8
+ palette?: Partial<ThemeConfig['palette']>;
9
+ };
10
+ /** Colors for the dark theme. Light and dark are separate, so one change never touches both. */
11
+ dark?: {
12
+ palette?: Partial<ThemeConfig['palette']>;
13
+ };
14
+ /** Tokens both themes share. */
15
+ shared?: {
16
+ radius?: Partial<ThemeConfig['radius']>;
17
+ shadow?: Partial<ThemeConfig['shadow']>;
18
+ zIndex?: Partial<ThemeConfig['zIndex']>;
19
+ /** By variant name (`label`, `body`, `stat`, ...); each takes only the fields it changes. */
20
+ typography?: Partial<Record<TypographyKey, TypographySpecs>>;
21
+ };
22
+ /** Sizes per density, e.g. `{ comfortable: { space: 16 } }`. */
23
+ density?: Partial<Record<Density, Partial<DensityTokens>>>;
24
+ }
25
+ /** `base` with `overrides` applied. Pure; neither argument is changed. */
26
+ export declare function applyThemeOverrides(base: ThemeConfig, mode: 'light' | 'dark', overrides: ThemeOverrides | undefined): ThemeConfig;
27
+ /** The density's tokens with `overrides` applied. */
28
+ export declare function densityTokens(density: Density, overrides: ThemeOverrides | undefined): DensityTokens;
@@ -0,0 +1,24 @@
1
+ import { DENSITY } from './density.js';
2
+ /** `base` with `overrides` applied. Pure; neither argument is changed. */
3
+ export function applyThemeOverrides(base, mode, overrides) {
4
+ if (!overrides) {
5
+ return base;
6
+ }
7
+ const shared = overrides.shared ?? {};
8
+ const typography = { ...base.typography };
9
+ for (const [variant, specs] of Object.entries(shared.typography ?? {})) {
10
+ typography[variant] = { ...typography[variant], ...specs };
11
+ }
12
+ return {
13
+ ...base,
14
+ radius: { ...base.radius, ...shared.radius },
15
+ shadow: { ...base.shadow, ...shared.shadow },
16
+ zIndex: { ...base.zIndex, ...shared.zIndex },
17
+ typography: typography,
18
+ palette: { ...base.palette, ...overrides[mode]?.palette },
19
+ };
20
+ }
21
+ /** The density's tokens with `overrides` applied. */
22
+ export function densityTokens(density, overrides) {
23
+ return { ...DENSITY[density], ...overrides?.density?.[density] };
24
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overrides.js","sourceRoot":"","sources":["../../src/theme/overrides.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAoC,MAAM,cAAc,CAAC;AAyBzE,0EAA0E;AAC1E,MAAM,UAAU,mBAAmB,CACjC,IAAiB,EACjB,IAAsB,EACtB,SAAqC;IAErC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,IAAI,EAAE,CAAC;IACtC,MAAM,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,EAAqC,CAAC;IAC7E,KAAK,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,CAAC;QACvE,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC;IAC7D,CAAC;IAED,OAAO;QACL,GAAG,IAAI;QACP,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE;QAC5C,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE;QAC5C,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE;QAC5C,UAAU,EAAE,UAAuC;QACnD,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE;KAC1D,CAAC;AACJ,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,aAAa,CAC3B,OAAgB,EAChB,SAAqC;IAErC,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,GAAG,SAAS,EAAE,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;AACnE,CAAC"}
@@ -0,0 +1,78 @@
1
+ import '@emotion/react';
2
+ import type { TypographySpecs } from 'e-prim';
3
+ import type { DensityTokens } from './density.ts';
4
+ declare module '@emotion/react' {
5
+ interface Theme {
6
+ /** Set by `HashsomeProvider` from its `density` prop. */
7
+ density: DensityTokens;
8
+ }
9
+ }
10
+ declare module 'e-prim' {
11
+ interface TPalette {
12
+ bg: string;
13
+ surface: string;
14
+ surfaceRaised: string;
15
+ text: string;
16
+ textMuted: string;
17
+ accent: string;
18
+ accentText: string;
19
+ /** Text drawn directly on the `accent` color (a solid-accent tile, or a dimmable tile's fill) — near-black in both themes, since light text on orange is low contrast. */
20
+ onAccent: string;
21
+ warm: string;
22
+ danger: string;
23
+ line: string;
24
+ /** The left nav rail / bottom nav dock's background — a third near-black, distinct from
25
+ * `bg`/`surface`. */
26
+ rail: string;
27
+ /** A subtle 1px hairline (e.g. `EntityDrawer`'s own edge) — used via the theme's `border`
28
+ * config, so components say `border` (boolean) rather than carrying the color themselves. */
29
+ border: string;
30
+ }
31
+ interface TRadius {
32
+ full: string;
33
+ card: string;
34
+ drawer: string;
35
+ /** The nav rail/dock's rounded-square icon buttons. */
36
+ chrome: string;
37
+ /** A status/pill row inside a drawer (e.g. `LightTile`'s power row). */
38
+ row: string;
39
+ /** A small chrome control (e.g. `EntityDrawer`'s expand/close buttons). */
40
+ small: string;
41
+ }
42
+ interface TShadow {
43
+ /** `EntityDrawer`'s own drop shadow. */
44
+ drawer: string;
45
+ }
46
+ interface TZIndex {
47
+ scrim: number;
48
+ drawer: number;
49
+ /** The page navigation (`NavRail`), fixed over a dashboard. */
50
+ nav: number;
51
+ /** A menu or status panel that drops from a control (the top bar's), above the page. */
52
+ dropdown: number;
53
+ /** A floating panel anchored to a control (the date picker's calendar), above what it sits in. */
54
+ popover: number;
55
+ }
56
+ interface TTypography {
57
+ /** A tile's own name (`Tile`'s label). */
58
+ label: TypographySpecs;
59
+ /** Muted status/secondary text (a tile's status line, a drawer row's timestamp). */
60
+ secondary: TypographySpecs;
61
+ /** A drawer's header name. */
62
+ heading: TypographySpecs;
63
+ /** Regular body copy inside a drawer (e.g. a history entry's message). */
64
+ body: TypographySpecs;
65
+ /** Emphasized body copy (a history entry's actor name, a status row's own label). */
66
+ bodyStrong: TypographySpecs;
67
+ /** Small uppercase, letter-spaced section labels ("History", a chart's "Power" label). */
68
+ eyebrow: TypographySpecs;
69
+ /** A large numeric readout (the energy chart's current wattage). */
70
+ stat: TypographySpecs;
71
+ /** A prominent heading, bigger than `heading` (the `TopBar`'s own title/dashboard switcher). */
72
+ title: TypographySpecs;
73
+ /** A `RoomHeader`'s title (a room name) — quiet, muted, not a heading that competes with the tiles. */
74
+ roomTitle: TypographySpecs;
75
+ /** The `TopBar`'s own clock reading — large enough to read at a glance from across a room. */
76
+ clock: TypographySpecs;
77
+ }
78
+ }
@@ -0,0 +1 @@
1
+ import '@emotion/react';
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/theme/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,gBAAgB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { TypographyKey } from 'e-prim';
2
+ /** A typography variant's resolved font size, for the rare consumer that can't render a
3
+ * `<Typography>` — e.g. an SVG chart's axis ticks. Everything else should use `<Typography>`. */
4
+ export declare function useTypographySize(key: Exclude<TypographyKey, 'default'>): number;
@@ -0,0 +1,9 @@
1
+ import { useTheme } from '@emotion/react';
2
+ /** A typography variant's resolved font size, for the rare consumer that can't render a
3
+ * `<Typography>` — e.g. an SVG chart's axis ticks. Everything else should use `<Typography>`. */
4
+ export function useTypographySize(key) {
5
+ const { typography } = useTheme();
6
+ const specs = typography;
7
+ const size = specs[key]?.fontSize ?? typography.default.fontSize;
8
+ return typeof size === 'number' ? size : 16;
9
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-typography.js","sourceRoot":"","sources":["../../src/theme/use-typography.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1C;iGACiG;AACjG,MAAM,UAAU,iBAAiB,CAAC,GAAsC;IACtE,MAAM,EAAE,UAAU,EAAE,GAAG,QAAQ,EAAE,CAAC;IAClC,MAAM,KAAK,GAAG,UAA2E,CAAC;IAC1F,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,QAAQ,IAAI,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC;IACjE,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { EntityRef, HistoryQuery, HistoryResult } from '@hashsome/core';
2
+ export interface EntityHistoryState {
3
+ /** `undefined` until the first answer, and always for a backend that keeps no history. */
4
+ result: HistoryResult | undefined;
5
+ loading: boolean;
6
+ }
7
+ /**
8
+ * An entity's past values from the backend's own record, fetched when `ref` or `query` change and
9
+ * kept for a minute. A backend that keeps no history, or an error, leaves `result` undefined —
10
+ * history is an extra, so a component just shows nothing. Pass `undefined` to not ask at all.
11
+ */
12
+ export declare function useEntityHistory(ref: EntityRef | undefined, query: HistoryQuery | undefined): EntityHistoryState;
@@ -0,0 +1,43 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useClient } from './provider.js';
3
+ /** How long an answer is reused: opening a drawer twice, or switching a range back, does not ask again. */
4
+ const FRESH_MS = 60_000;
5
+ const cache = new WeakMap();
6
+ function load(client, ref, query) {
7
+ const answers = cache.get(client) ?? new Map();
8
+ cache.set(client, answers);
9
+ const key = `${ref}|${query.range}|${query.bucket ?? ''}`;
10
+ const known = answers.get(key);
11
+ if (known && Date.now() - known.at < FRESH_MS) {
12
+ return known.result;
13
+ }
14
+ const result = client.history(ref, query);
15
+ answers.set(key, { at: Date.now(), result });
16
+ // A failed answer is not worth keeping: the next ask tries again.
17
+ result.catch(() => answers.delete(key));
18
+ return result;
19
+ }
20
+ /**
21
+ * An entity's past values from the backend's own record, fetched when `ref` or `query` change and
22
+ * kept for a minute. A backend that keeps no history, or an error, leaves `result` undefined —
23
+ * history is an extra, so a component just shows nothing. Pass `undefined` to not ask at all.
24
+ */
25
+ export function useEntityHistory(ref, query) {
26
+ const client = useClient();
27
+ const range = query?.range;
28
+ const bucket = query?.bucket;
29
+ const [state, setState] = useState();
30
+ const key = ref && range ? `${ref}|${range}|${bucket ?? ''}` : undefined;
31
+ useEffect(() => {
32
+ if (!ref || !range || !key) {
33
+ return;
34
+ }
35
+ let current = true;
36
+ load(client, ref, { range, ...(bucket ? { bucket } : {}) }).then((result) => current && setState({ key, result }), () => current && setState({ key, result: undefined }));
37
+ return () => {
38
+ current = false;
39
+ };
40
+ }, [client, ref, range, bucket, key]);
41
+ const answered = state !== undefined && state.key === key;
42
+ return { result: answered ? state.result : undefined, loading: key !== undefined && !answered };
43
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-entity-history.js","sourceRoot":"","sources":["../src/use-entity-history.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { BrowseItem, EntityRef } from '@hashsome/core';
2
+ export interface MediaBrowserState {
3
+ /** What is listed now. */
4
+ items: BrowseItem[];
5
+ /** A name for the list: the opened folder's, or what was searched for. Absent on a tab. */
6
+ title: string | undefined;
7
+ loading: boolean;
8
+ error: string | undefined;
9
+ /** The top level's shelves (Playlists, Albums, ...), when it is a set of folders to switch
10
+ * between. They are shown as tabs instead of a list of folders to open, and stay in view while
11
+ * something inside a shelf is open or a search is showing. */
12
+ tabs: BrowseItem[] | undefined;
13
+ /** The open tab; none while a search is showing, since its results belong to no shelf. */
14
+ activeTab: string | undefined;
15
+ /** Goes to a shelf: leaves whatever was open inside one, and any search. */
16
+ selectTab(id: string): void;
17
+ /** Opened a folder (or searching), so there is somewhere to go back to. */
18
+ canGoBack: boolean;
19
+ searching: boolean;
20
+ open(item: BrowseItem): void;
21
+ back(): void;
22
+ /** What is typed in the search box. */
23
+ query: string;
24
+ /** Lists what matches `text`; an empty string goes back to where the browser was. */
25
+ search(text: string): void;
26
+ }
27
+ /** Walks a player's own media library through the runtime. The top level is read once: shelves
28
+ * become tabs (the first is open to begin with), anything else is a plain list. Below it, a stack
29
+ * of opened folders; apart from it, an optional search, and clearing the search returns to where
30
+ * the browser was. */
31
+ export declare function useMediaBrowser(ref: EntityRef): MediaBrowserState;
@@ -0,0 +1,87 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { useClient } from './provider.js';
3
+ /** A top level that is nothing but folders to look inside is a set of shelves, which read better
4
+ * as tabs than as a list that always looks the same. */
5
+ const isShelves = (items) => items.length >= 2 && items.every((item) => item.expandable && !item.playable);
6
+ const messageOf = (error) => (error instanceof Error ? error.message : String(error));
7
+ /** Walks a player's own media library through the runtime. The top level is read once: shelves
8
+ * become tabs (the first is open to begin with), anything else is a plain list. Below it, a stack
9
+ * of opened folders; apart from it, an optional search, and clearing the search returns to where
10
+ * the browser was. */
11
+ export function useMediaBrowser(ref) {
12
+ const client = useClient();
13
+ const [trail, setTrail] = useState([{}]);
14
+ const [text, setText] = useState('');
15
+ const [tab, setTab] = useState();
16
+ // Answers are kept with the request they answer, so a stale one is never shown for a new one.
17
+ const [top, setTop] = useState(null);
18
+ const [answer, setAnswer] = useState(null);
19
+ useEffect(() => {
20
+ let current = true;
21
+ client.browse(ref, {}).then((level) => current && setTop({ key: 'top', ...level }), (error) => current && setTop({ key: 'top', items: [], error: messageOf(error) }));
22
+ return () => {
23
+ current = false;
24
+ };
25
+ }, [client, ref]);
26
+ const needle = text.trim();
27
+ const searching = needle !== '';
28
+ const here = trail.at(-1) ?? {};
29
+ const atTop = trail.length === 1;
30
+ const tabs = top && !top.error && isShelves(top.items) ? top.items : undefined;
31
+ const selected = tabs ? (tabs.find((item) => item.id === tab) ?? tabs[0])?.id : undefined;
32
+ // What to ask for, if anything: a search, a folder, or the open tab. A top level that is not
33
+ // shelves is already in hand.
34
+ const path = atTop ? selected : here.path;
35
+ const key = searching ? `search:${needle}` : `path:${path ?? ''}`;
36
+ const result = answer?.key === key ? answer : null;
37
+ useEffect(() => {
38
+ const request = searching ? { search: needle } : path === undefined ? undefined : { path };
39
+ if (!request) {
40
+ return;
41
+ }
42
+ let current = true;
43
+ const timer = setTimeout(() => {
44
+ client.browse(ref, request).then((level) => current && setAnswer({ key, ...level }), (error) => current && setAnswer({ key, items: [], error: messageOf(error) }));
45
+ },
46
+ // Typing in the search box waits for a pause; opening a folder or a tab does not.
47
+ searching ? 300 : 0);
48
+ return () => {
49
+ current = false;
50
+ clearTimeout(timer);
51
+ };
52
+ }, [client, ref, key, searching, needle, path]);
53
+ const open = useCallback((item) => setTrail((levels) => [...levels, { path: item.id, title: item.title }]), []);
54
+ const selectTab = useCallback((id) => {
55
+ setTab(id);
56
+ setText('');
57
+ setTrail([{}]);
58
+ }, []);
59
+ const back = useCallback(() => {
60
+ if (text.trim() !== '') {
61
+ setText('');
62
+ return;
63
+ }
64
+ setTrail((levels) => (levels.length > 1 ? levels.slice(0, -1) : levels));
65
+ }, [text]);
66
+ // The plain top-level list, when there are no shelves to make tabs of.
67
+ const listing = !searching && atTop && !tabs ? top : result;
68
+ return {
69
+ items: listing?.items ?? [],
70
+ title: searching
71
+ ? (result?.title ?? `Results for “${needle}”`)
72
+ : atTop
73
+ ? undefined
74
+ : (result?.title ?? here.title),
75
+ loading: listing === null,
76
+ error: listing?.error,
77
+ tabs,
78
+ activeTab: searching ? undefined : selected,
79
+ selectTab,
80
+ canGoBack: searching || !atTop,
81
+ searching,
82
+ open,
83
+ back,
84
+ query: text,
85
+ search: setText,
86
+ };
87
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-media-browser.js","sourceRoot":"","sources":["../src/use-media-browser.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@hashsome/ui",
3
+ "version": "0.1.0",
4
+ "description": "Design system components and hooks",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "default": "./dist/index.js"
10
+ },
11
+ "./package.json": "./package.json",
12
+ "./gallery": {
13
+ "types": "./dist/gallery/index.d.ts",
14
+ "default": "./dist/gallery/index.js"
15
+ }
16
+ },
17
+ "dependencies": {
18
+ "@emotion/react": "11.14.0",
19
+ "@hashsome/core": "0.1.0",
20
+ "e-prim": "2.0.1",
21
+ "motion": "13.4.5",
22
+ "react": "19.3.0",
23
+ "react-router": "8.4.0",
24
+ "recharts": "3.10.1"
25
+ },
26
+ "devDependencies": {
27
+ "@testing-library/react": "16.3.3",
28
+ "@testing-library/user-event": "14.6.7",
29
+ "@types/react": "19.3.0",
30
+ "@types/react-dom": "19.3.0",
31
+ "@vitejs/plugin-react": "6.1.1",
32
+ "jsdom": "30.1.1",
33
+ "react-dom": "19.3.0",
34
+ "vite": "8.3.1"
35
+ },
36
+ "license": "GPL-3.0-only",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/dstoyanoff/hashsome.git",
40
+ "directory": "packages/ui"
41
+ },
42
+ "homepage": "https://github.com/dstoyanoff/hashsome#readme",
43
+ "bugs": "https://github.com/dstoyanoff/hashsome/issues",
44
+ "files": [
45
+ "dist"
46
+ ],
47
+ "publishConfig": {
48
+ "access": "public",
49
+ "provenance": true
50
+ },
51
+ "scripts": {
52
+ "typecheck": "tsc --noEmit",
53
+ "docs": "vite",
54
+ "docs:build": "vite build",
55
+ "build": "tsc -b tsconfig.build.json"
56
+ }
57
+ }