@glasshome/ui 1.4.0 → 1.6.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.
@@ -0,0 +1,4 @@
1
+ import { MediaStoreError } from "./media-store.js";
2
+ export declare function toMediaStoreError(cause: unknown): MediaStoreError;
3
+ export declare function mediaStoreErrorCopy(error: MediaStoreError): string;
4
+ export declare function mediaIndexErrorCopy(error: MediaStoreError): string;
@@ -0,0 +1,55 @@
1
+ export type StoredMedia = {
2
+ id: string;
3
+ mimeType: string;
4
+ width?: number;
5
+ height?: number;
6
+ size: number;
7
+ /** Server-computed count of live widget configs referencing this id. */
8
+ usedBy: number;
9
+ };
10
+ export type MediaQuotaUsage = {
11
+ bytes: number;
12
+ limitBytes: number;
13
+ files: number;
14
+ limitFiles: number;
15
+ };
16
+ export type MediaStoreErrorKind = "file_too_large" | "image_too_large" | "not_an_image" | "quota_bytes_exceeded" | "quota_files_exceeded" | "upload_rate_exceeded" | "server_storage_full" | "no_active_household" | "upload_failed";
17
+ export declare class MediaStoreError extends Error {
18
+ readonly kind: MediaStoreErrorKind;
19
+ readonly usage?: MediaQuotaUsage;
20
+ constructor(kind: MediaStoreErrorKind, usage?: MediaQuotaUsage);
21
+ }
22
+ /** One gallery open, one query: the server counts usage in the same scan. */
23
+ export type MediaIndex = {
24
+ media: StoredMedia[];
25
+ usage: MediaQuotaUsage;
26
+ };
27
+ /** "thumb" is a server-side derivative, longest edge ~400px, for grids. */
28
+ export type MediaVariant = "original" | "thumb";
29
+ export interface MediaStore {
30
+ index(): Promise<MediaIndex>;
31
+ upload(file: File): Promise<StoredMedia>;
32
+ remove(id: string): Promise<void>;
33
+ url(id: string, variant?: MediaVariant): string;
34
+ }
35
+ export declare const MediaStoreContext: import("solid-js").Context<MediaStore | undefined>;
36
+ /** Host apps call this once at startup; MediaStoreContext overrides it per-tree. */
37
+ export declare function provideMediaStore(store: MediaStore): void;
38
+ export declare function useMediaStore(): MediaStore | undefined;
39
+ /** One grid page. The picker and the library both slice by this, so neither can
40
+ * mount a whole 500-file library at once. */
41
+ export declare const MEDIA_PAGE_SIZE = 24;
42
+ /** A row whose mime is missing or not a string is not an image; a throw here would
43
+ * take the surrounding gallery down with it. */
44
+ export declare function isMediaImage(item: StoredMedia): boolean;
45
+ /** Unused first: someone browsing stored media is looking for what is safe to delete. */
46
+ export declare function sortMediaForClearing(media: StoredMedia[]): StoredMedia[];
47
+ export interface BrokenMedia {
48
+ isBroken: (item: StoredMedia) => boolean;
49
+ markBroken: (item: StoredMedia) => void;
50
+ }
51
+ /** Keyed by the url that failed, so a re-uploaded id (new url) starts unbroken again. */
52
+ export declare function createBrokenMedia(thumbUrl: (id: string) => string): BrokenMedia;
53
+ export declare function mediaUrl(id: string | null | undefined): string | undefined;
54
+ /** Reads better than mediaUrl in an image widget's source. */
55
+ export declare const imageUrl: typeof mediaUrl;
@@ -0,0 +1,15 @@
1
+ import type { StoredMedia } from "./media-store.js";
2
+ export interface MediaTileProps {
3
+ item: StoredMedia;
4
+ thumbUrl: string;
5
+ /** Describes what the click does here: the picker chooses, a library opens a preview. */
6
+ label: string;
7
+ broken: boolean;
8
+ onSelect: () => void;
9
+ onBroken: () => void;
10
+ onDelete?: () => void;
11
+ markUnused?: boolean;
12
+ selected?: boolean;
13
+ class?: string;
14
+ }
15
+ export declare function MediaTile(props: MediaTileProps): import("solid-js").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import { type JSX } from "solid-js";
2
+ export declare function OptionCardGroup(props: {
3
+ value: string | null;
4
+ onChange: (value: string) => void;
5
+ "aria-label"?: string;
6
+ class?: string;
7
+ children: JSX.Element;
8
+ }): JSX.Element;
9
+ export declare function OptionCard(props: {
10
+ value: string;
11
+ title: string;
12
+ description?: string;
13
+ icon?: string;
14
+ disabled?: boolean;
15
+ class?: string;
16
+ children?: JSX.Element;
17
+ }): JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { type Component, type ComponentProps, type JSX } from "solid-js";
2
+ import { Input } from "./input.js";
3
+ declare const PasswordInput: Component<Omit<ComponentProps<typeof Input>, "type"> & {
4
+ /** Rendered left-aligned inside the field (e.g. a lock glyph). */
5
+ leading?: JSX.Element;
6
+ showLabel?: string;
7
+ hideLabel?: string;
8
+ }>;
9
+ export { PasswordInput };
@@ -11,7 +11,11 @@ declare const Popover: typeof import("@kobalte/core/popover").Root & {
11
11
  Trigger: typeof import("@kobalte/core/popover").Trigger;
12
12
  };
13
13
  declare const PopoverTrigger: Component<ComponentProps<typeof PopoverPrimitive.Trigger>>;
14
- declare const PopoverContent: Component<ComponentProps<typeof PopoverPrimitive.Content>>;
14
+ type PopoverContentProps = ComponentProps<typeof PopoverPrimitive.Content> & {
15
+ /** "field" wears the concave input surface, the way SelectContent does. */
16
+ surface?: "overlay" | "field";
17
+ };
18
+ declare const PopoverContent: Component<PopoverContentProps>;
15
19
  declare const PopoverAnchor: Component<ComponentProps<typeof PopoverPrimitive.Anchor>>;
16
20
  declare const anchorToTriggerTop: (anchor?: HTMLElement) => {
17
21
  x: number;
@@ -1,5 +1,7 @@
1
1
  import { RadioGroup as RadioGroupPrimitive } from "@kobalte/core/radio-group";
2
2
  import { type Component, type ComponentProps } from "solid-js";
3
3
  declare const RadioGroup: Component<ComponentProps<typeof RadioGroupPrimitive>>;
4
- declare const RadioGroupItem: Component<ComponentProps<typeof RadioGroupPrimitive.Item>>;
4
+ declare const RadioGroupItem: Component<ComponentProps<typeof RadioGroupPrimitive.Item> & {
5
+ showControl?: boolean;
6
+ }>;
5
7
  export { RadioGroup, RadioGroupItem };
@@ -1,4 +1,6 @@
1
+ import type { VariantProps } from "cva";
1
2
  import { type Component, type ComponentProps, type JSX, type ParentComponent } from "solid-js";
3
+ import type { buttonVariants } from "../lib/button-variants.js";
2
4
  interface ResponsiveDialogProps {
3
5
  children: JSX.Element;
4
6
  open?: boolean;
@@ -12,5 +14,7 @@ declare const ResponsiveDialogHeader: Component<ComponentProps<"div">>;
12
14
  declare const ResponsiveDialogTitle: Component<ComponentProps<"h2">>;
13
15
  declare const ResponsiveDialogDescription: Component<ComponentProps<"p">>;
14
16
  declare const ResponsiveDialogFooter: Component<ComponentProps<"div">>;
15
- declare const ResponsiveDialogClose: ParentComponent<ComponentProps<"button">>;
17
+ /** Defaults to the footer's outline button; an icon-only close passes `ghost`
18
+ * so the glyph is not boxed. */
19
+ declare const ResponsiveDialogClose: ParentComponent<ComponentProps<"button"> & VariantProps<typeof buttonVariants>>;
16
20
  export { ResponsiveDialog, ResponsiveDialogClose, ResponsiveDialogContent, ResponsiveDialogDescription, ResponsiveDialogFooter, ResponsiveDialogHeader, ResponsiveDialogTitle, ResponsiveDialogTrigger, };
@@ -22,7 +22,7 @@ export interface ExtendedJSONSchema extends JSONSchema7 {
22
22
  * schemas with formTypes it predates). Kept in lockstep with the SDK's field
23
23
  * helpers by dash's formType parity test.
24
24
  */
25
- export declare const SCHEMA_FORM_FORM_TYPES: readonly ["icon-picker", "area-picker", "list", "variants"];
25
+ export declare const SCHEMA_FORM_FORM_TYPES: readonly ["icon-picker", "image-picker", "area-picker", "list", "variants"];
26
26
  /**
27
27
  * New-item defaults from the wire schema: the serialized `default` when the
28
28
  * SDK provided one (field.variants always does), else property defaults; for
@@ -36,6 +36,10 @@ export declare function LabeledInput(props: {
36
36
  }): JSX.Element;
37
37
  export declare function SwitchRow(props: {
38
38
  label: string;
39
+ description?: string;
40
+ /** Iconify id shown before the label (sensitive-device rows, platform toggles). */
41
+ icon?: string;
39
42
  checked: boolean;
43
+ disabled?: boolean;
40
44
  onChange: (value: boolean) => void;
41
45
  }): JSX.Element;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Position inside a multi-step flow: dots for the eye, one spoken line for
3
+ * screen readers. The dots carry no text of their own, so the sr-only line is
4
+ * the only place the position is readable.
5
+ */
6
+ export declare function StepIndicator(props: {
7
+ count: number;
8
+ index: number;
9
+ class?: string;
10
+ }): import("solid-js").JSX.Element;
@@ -1,11 +1,8 @@
1
- import { type Component } from "solid-js";
2
- interface SwitchProps {
1
+ import { type Component, type ComponentProps } from "solid-js";
2
+ type SwitchProps = Omit<ComponentProps<"button">, "onChange" | "children"> & {
3
3
  checked?: boolean;
4
4
  defaultChecked?: boolean;
5
5
  onChange?: (checked: boolean) => void;
6
- disabled?: boolean;
7
- class?: string;
8
- name?: string;
9
- }
6
+ };
10
7
  declare const Switch: Component<SwitchProps>;
11
8
  export { Switch };
@@ -152,10 +152,14 @@ var m = (e) => typeof e == "boolean" ? `${e}` : e === 0 ? "0" : e, h = [], { com
152
152
  none: ""
153
153
  }, T = "cursor-pointer transition-colors duration-200 hover:border-border hover:bg-foreground/[0.03] focus-within:border-border", E = "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] md:px-4", D = "font-semibold leading-none", O = "text-muted-foreground text-sm", k = "col-start-2 row-span-2 row-start-1 self-start justify-self-end", A = "px-3 md:px-4", j = "flex items-center px-3 md:px-4 [.border-t]:pt-6", ae = "h-full overflow-hidden";
154
154
  function oe(e, t = "horizontal") {
155
- return e === "slide" ? `flex ${t === "horizontal" ? "-ml-4" : "-mt-4 flex-col"}` : "carousel-stack";
155
+ if (e === "wipe") return "carousel-stack";
156
+ let n = t === "vertical";
157
+ return e === "fade" ? n ? "flex flex-col" : "flex" : `flex ${n ? "-mt-4 flex-col" : "-ml-4"}`;
156
158
  }
157
159
  function se(e, t = "horizontal") {
158
- return e === "wipe" ? "min-w-0 carousel-wipe" : e === "fade" ? "min-w-0" : `min-w-0 shrink-0 grow-0 basis-full ${t === "horizontal" ? "pl-4" : "pt-4"}`;
160
+ if (e === "wipe") return "min-w-0 carousel-wipe";
161
+ let n = "min-w-0 shrink-0 grow-0 basis-full";
162
+ return e === "fade" ? n : `${n} ${t === "horizontal" ? "pl-4" : "pt-4"}`;
159
163
  }
160
164
  var M = "flex items-center justify-center gap-2";
161
165
  function N(e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glasshome/ui",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "SolidJS component library for GlassHome, built on Kobalte",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/glasshome/ui#readme",
@@ -6,24 +6,35 @@ export type CarouselTransition = "slide" | "fade" | "wipe";
6
6
 
7
7
  export const CAROUSEL_VIEWPORT = "h-full overflow-hidden";
8
8
 
9
- /** Stacked modes put every slide in one grid cell; slide lays out a flex track. */
9
+ /**
10
+ * Only wipe stacks in CSS. Fade keeps embla's standard flex track, which is the
11
+ * structure its plugin is built for: embla reads each snap from the slide's
12
+ * offset along the axis, and the plugin then translates every slide back onto
13
+ * the viewport to cross-fade them. Slides sharing one grid cell share offset 0,
14
+ * so every snap collapses onto the same point and a pointer drag has no
15
+ * distance to resolve: it never changes slide. Index-driven navigation
16
+ * (autoplay, arrows, dots) survives the collapse, which is why wipe still
17
+ * advances.
18
+ */
10
19
  export function carouselTrack(
11
20
  transition: CarouselTransition,
12
21
  orientation: "horizontal" | "vertical" = "horizontal",
13
22
  ) {
14
- return transition === "slide"
15
- ? `flex ${orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col"}`
16
- : "carousel-stack";
23
+ if (transition === "wipe") return "carousel-stack";
24
+ const vertical = orientation === "vertical";
25
+ // Fade slides sit on top of each other, so the track carries no gutter.
26
+ if (transition === "fade") return vertical ? "flex flex-col" : "flex";
27
+ return `flex ${vertical ? "-mt-4 flex-col" : "-ml-4"}`;
17
28
  }
18
29
 
19
30
  export function carouselItem(
20
31
  transition: CarouselTransition,
21
32
  orientation: "horizontal" | "vertical" = "horizontal",
22
33
  ) {
23
- // Stacked slides fill their grid cell; only a flex track needs basis/gutter.
24
34
  if (transition === "wipe") return "min-w-0 carousel-wipe";
25
- if (transition === "fade") return "min-w-0";
26
- return `min-w-0 shrink-0 grow-0 basis-full ${orientation === "horizontal" ? "pl-4" : "pt-4"}`;
35
+ const track = "min-w-0 shrink-0 grow-0 basis-full";
36
+ if (transition === "fade") return track;
37
+ return `${track} ${orientation === "horizontal" ? "pl-4" : "pt-4"}`;
27
38
  }
28
39
 
29
40
  export const CAROUSEL_DOTS = "flex items-center justify-center gap-2";
@@ -1,7 +1,7 @@
1
1
  /* Metallic tier chip: 135deg brushed sweep, bright ends, dip in the middle. */
2
2
 
3
3
  export const TIER_BADGE_CLASS =
4
- "inline-flex cursor-default select-none items-center rounded-full px-2 py-0.5 font-black text-[10px] uppercase tracking-wider";
4
+ "inline-flex cursor-default select-none items-center rounded-full px-2 py-0.5 font-black text-[10px] tracking-wide";
5
5
 
6
6
  /** Optional sweep shaping. Default is the symmetric brushed look (both ends
7
7
  * `hi`, dipping to `lo` at the midpoint). The collectible/license golds are a
@@ -192,28 +192,29 @@
192
192
  }
193
193
 
194
194
  /**
195
- * Consistent, theme-aware thin scrollbar across browsers. Firefox uses
196
- * scrollbar-width/color; Chromium/WebKit use the ::-webkit-scrollbar pseudos.
197
- * Applied by the <ScrollArea> component so every scroll surface reads the same.
195
+ * Consistent, theme-aware overlay scrollbar across browsers. Firefox uses
196
+ * scrollbar-width/color; Chromium/WebKit use the ::-webkit-scrollbar pseudos
197
+ * (no stepper-button pseudo is declared, so none render). Applied by every
198
+ * scroll surface that isn't a bare page — <ScrollArea>, dialogs, sheets.
198
199
  */
199
200
  .gh-scroll {
200
201
  scrollbar-width: thin;
201
- scrollbar-color: var(--border) transparent;
202
+ scrollbar-color: color-mix(in srgb, var(--foreground) 22%, transparent) transparent;
202
203
  &::-webkit-scrollbar {
203
- width: 10px;
204
- height: 10px;
204
+ width: 8px;
205
+ height: 8px;
205
206
  }
206
207
  &::-webkit-scrollbar-track {
207
208
  background: transparent;
208
209
  }
209
210
  &::-webkit-scrollbar-thumb {
210
- background: var(--border);
211
+ background: color-mix(in srgb, var(--foreground) 22%, transparent);
211
212
  border-radius: 9999px;
212
213
  border: 2px solid transparent;
213
214
  background-clip: padding-box;
214
215
  }
215
216
  &::-webkit-scrollbar-thumb:hover {
216
- background: var(--muted-foreground);
217
+ background: color-mix(in srgb, var(--foreground) 38%, transparent);
217
218
  }
218
219
  }
219
220
 
@@ -233,7 +234,7 @@
233
234
  margin-top: 0.125rem;
234
235
  }
235
236
 
236
- /* Stacked carousel modes (fade, wipe) put every slide in one grid cell. This is
237
+ /* Carousel `wipe` puts every slide in one grid cell. This is
237
238
  * structural, so it ships as real CSS: a Tailwind arbitrary variant written in
238
239
  * package source never reaches a consumer, because Tailwind skips node_modules
239
240
  * when scanning for class names. */
@@ -261,10 +261,6 @@
261
261
  label:has(> input:is([type="checkbox"], [type="radio"]):not(:disabled)) {
262
262
  cursor: pointer;
263
263
  }
264
- /* Old embedded WebViews (Shelly Wall Display) native-paint appearance:button as a gray box. */
265
- button {
266
- appearance: none;
267
- }
268
264
  }
269
265
 
270
266
  /* Utility classes */