@camstack/ui-library 1.2.335 → 1.2.337

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/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ All notable changes to this package are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.2.337] - 2026-10-27
11
+
12
+ ### Added
13
+
14
+ - The published package now includes its README and CHANGELOG.
15
+
16
+ ## [1.2.336] - 2026-10-27
17
+
18
+ ### Added
19
+
20
+ - CamStack UI Library (@camstack/ui-library).
package/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # CamStack UI Library
2
+
3
+ The shared React component library and Tailwind theme the CamStack admin UI, addon pages and widgets are built from.
4
+
5
+ <!-- readme:generated:start (derived from the manifest; edit outside this block) -->
6
+
7
+ Package `@camstack/ui-library` on npm.
8
+
9
+ ## Documentation
10
+
11
+ - [docs/architecture/ui.md](https://github.com/camstack/server/blob/main/docs/architecture/ui.md)
12
+ - [docs/ui/conventions.md](https://github.com/camstack/server/blob/main/docs/ui/conventions.md)
13
+ - [docs/ui/admin-patterns.md](https://github.com/camstack/server/blob/main/docs/ui/admin-patterns.md)
14
+ - [docs/ui/common-components.md](https://github.com/camstack/server/blob/main/docs/ui/common-components.md)
15
+ - [packages/ui-library/AGENTS.md](https://github.com/camstack/server/blob/main/packages/ui-library/AGENTS.md)
16
+
17
+ See [CHANGELOG.md](./CHANGELOG.md) for what changed in each version.
18
+
19
+ <!-- readme:generated:end -->
@@ -1,5 +1,5 @@
1
1
  import { t as Settings2 } from "./settings-2-jT1iBqHD.js";
2
- import { A as Trash2, SCENE_CAPTURE_VARIANTS, Sr as Camera, TEXT_HINT, UiTrans, badgeFor, coverageLine, fn as ImageOff, hubPath, isAbsentProvider, pt as RotateCcw, roiTooLarge, statusLine, useDeviceProxy, useDeviceSnapshotImage, useDeviceStateSlice, useSystem, useUiText, variantLabel } from "./index.js";
2
+ import { A as Trash2, SCENE_CAPTURE_VARIANTS, Sr as Camera, TEXT_HINT, UiTrans, badgeFor, coverageLine, fn as ImageOff, hubPath, isAbsentProvider, pt as RotateCcw, roiTooLarge, statusLine, useConfirm, useDeviceProxy, useDeviceSnapshotImage, useDeviceStateSlice, useSystem, useUiText, variantLabel } from "./index.js";
3
3
  import { MaskShapeCanvas } from "./MaskShapeCanvas-C9cJlxvG.js";
4
4
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
5
5
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -92,7 +92,7 @@ function SceneReferenceGallery({ monitor, disabled, onDelete }) {
92
92
  const t = useUiText();
93
93
  const groups = groupReferences(monitor);
94
94
  const total = totalReferences(monitor);
95
- const [confirming, setConfirming] = useState(null);
95
+ const confirm = useConfirm();
96
96
  if (groups.length === 0) return /* @__PURE__ */ jsx("p", {
97
97
  className: `${TEXT_HINT} leading-relaxed`,
98
98
  children: t("uiLibrary.sceneReferenceGallery.empty", "No photographs captured yet. Press “Capture daylight” to take the one this scene will judge everything against.")
@@ -113,7 +113,6 @@ function SceneReferenceGallery({ monitor, disabled, onDelete }) {
113
113
  children: group.tiles.map((tile) => {
114
114
  const key = `${tile.stateId}:${String(tile.index)}`;
115
115
  const isLastOverall = total === 1;
116
- const pending = confirming === key;
117
116
  return /* @__PURE__ */ jsxs("li", {
118
117
  className: "flex w-24 flex-col gap-1 rounded-md border border-border p-1",
119
118
  children: [
@@ -131,21 +130,22 @@ function SceneReferenceGallery({ monitor, disabled, onDelete }) {
131
130
  date: formatCapturedAt(tile.reference.capturedAt)
132
131
  }),
133
132
  onClick: () => {
134
- if (isLastOverall && !pending) {
135
- setConfirming(key);
136
- return;
137
- }
138
- setConfirming(null);
139
- onDelete({
140
- stateId: tile.stateId,
141
- index: tile.index
142
- });
133
+ (async () => {
134
+ if (isLastOverall) {
135
+ if (!await confirm({
136
+ title: t("uiLibrary.sceneReferenceGallery.deleteTitle", "Delete the only photograph?"),
137
+ message: t("uiLibrary.sceneReferenceGallery.lastWarning", "This is the only photograph this scene has. Delete it and the scene no longer knows what normal looks like — it stops judging until you capture again."),
138
+ confirmLabel: t("uiLibrary.sceneReferenceGallery.delete", "Delete"),
139
+ variant: "danger"
140
+ })) return;
141
+ }
142
+ onDelete({
143
+ stateId: tile.stateId,
144
+ index: tile.index
145
+ });
146
+ })();
143
147
  },
144
- children: [/* @__PURE__ */ jsx(Trash2, { className: "mr-1 inline h-3 w-3" }), pending ? t("uiLibrary.sceneReferenceGallery.confirm", "Confirm") : t("uiLibrary.sceneReferenceGallery.delete", "Delete")]
145
- }),
146
- pending && /* @__PURE__ */ jsx("p", {
147
- className: "text-[10px] leading-snug text-warning",
148
- children: t("uiLibrary.sceneReferenceGallery.lastWarning", "This is the only photograph this scene has. Delete it and the scene no longer knows what normal looks like — it stops judging until you capture again.")
148
+ children: [/* @__PURE__ */ jsx(Trash2, { className: "mr-1 inline h-3 w-3" }), t("uiLibrary.sceneReferenceGallery.delete", "Delete")]
149
149
  })
150
150
  ]
151
151
  }, key);
@@ -92,7 +92,7 @@ function SceneReferenceGallery({ monitor, disabled, onDelete }) {
92
92
  const t = require_index.useUiText();
93
93
  const groups = groupReferences(monitor);
94
94
  const total = totalReferences(monitor);
95
- const [confirming, setConfirming] = (0, react.useState)(null);
95
+ const confirm = require_index.useConfirm$1();
96
96
  if (groups.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
97
97
  className: `${require_index.TEXT_HINT} leading-relaxed`,
98
98
  children: t("uiLibrary.sceneReferenceGallery.empty", "No photographs captured yet. Press “Capture daylight” to take the one this scene will judge everything against.")
@@ -113,7 +113,6 @@ function SceneReferenceGallery({ monitor, disabled, onDelete }) {
113
113
  children: group.tiles.map((tile) => {
114
114
  const key = `${tile.stateId}:${String(tile.index)}`;
115
115
  const isLastOverall = total === 1;
116
- const pending = confirming === key;
117
116
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
118
117
  className: "flex w-24 flex-col gap-1 rounded-md border border-border p-1",
119
118
  children: [
@@ -131,21 +130,22 @@ function SceneReferenceGallery({ monitor, disabled, onDelete }) {
131
130
  date: formatCapturedAt(tile.reference.capturedAt)
132
131
  }),
133
132
  onClick: () => {
134
- if (isLastOverall && !pending) {
135
- setConfirming(key);
136
- return;
137
- }
138
- setConfirming(null);
139
- onDelete({
140
- stateId: tile.stateId,
141
- index: tile.index
142
- });
133
+ (async () => {
134
+ if (isLastOverall) {
135
+ if (!await confirm({
136
+ title: t("uiLibrary.sceneReferenceGallery.deleteTitle", "Delete the only photograph?"),
137
+ message: t("uiLibrary.sceneReferenceGallery.lastWarning", "This is the only photograph this scene has. Delete it and the scene no longer knows what normal looks like — it stops judging until you capture again."),
138
+ confirmLabel: t("uiLibrary.sceneReferenceGallery.delete", "Delete"),
139
+ variant: "danger"
140
+ })) return;
141
+ }
142
+ onDelete({
143
+ stateId: tile.stateId,
144
+ index: tile.index
145
+ });
146
+ })();
143
147
  },
144
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.Trash2, { className: "mr-1 inline h-3 w-3" }), pending ? t("uiLibrary.sceneReferenceGallery.confirm", "Confirm") : t("uiLibrary.sceneReferenceGallery.delete", "Delete")]
145
- }),
146
- pending && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
147
- className: "text-[10px] leading-snug text-warning",
148
- children: t("uiLibrary.sceneReferenceGallery.lastWarning", "This is the only photograph this scene has. Delete it and the scene no longer knows what normal looks like — it stops judging until you capture again.")
148
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.Trash2, { className: "mr-1 inline h-3 w-3" }), t("uiLibrary.sceneReferenceGallery.delete", "Delete")]
149
149
  })
150
150
  ]
151
151
  }, key);
@@ -1,4 +1,4 @@
1
- import { type ReactNode } from "react";
1
+ import type { ReactNode } from "react";
2
2
  export interface DeviceBatchToolbarProps {
3
3
  readonly selectedIds: ReadonlySet<number>;
4
4
  readonly onClear: () => void;
package/dist/index.cjs CHANGED
@@ -56566,7 +56566,7 @@ function PrimaryChildPicker({ childrenList, selectedId, onSetPrimary, disabled,
56566
56566
  //#region src/composites/device-list/batch-toolbar.tsx
56567
56567
  function DeviceBatchToolbar({ selectedIds, onClear, remove, disable, enable }) {
56568
56568
  const t = useUiText();
56569
- const [pendingDelete, setPendingDelete] = (0, react$1.useState)(false);
56569
+ const confirm = useConfirm();
56570
56570
  if (selectedIds.size === 0) return null;
56571
56571
  const ids = [...selectedIds];
56572
56572
  const count = ids.length;
@@ -56578,19 +56578,14 @@ function DeviceBatchToolbar({ selectedIds, onClear, remove, disable, enable }) {
56578
56578
  for (const id of ids) enable({ deviceId: id });
56579
56579
  onClear();
56580
56580
  };
56581
- const handleDeleteFirst = () => {
56582
- setPendingDelete(true);
56583
- };
56584
- const handleDeleteConfirm = () => {
56581
+ const handleDelete = async () => {
56582
+ if (!await confirm({
56583
+ title: t("uiLibrary.batchToolbar.deleteTitle", "Delete {{count}} devices?", { count }),
56584
+ message: t("uiLibrary.batchToolbar.deleteMessage", "{{count}} selected devices will be deleted. This cannot be undone.", { count }),
56585
+ confirmLabel: t("uiLibrary.batchToolbar.delete", "Delete"),
56586
+ variant: "danger"
56587
+ })) return;
56585
56588
  for (const id of ids) remove({ deviceId: id });
56586
- setPendingDelete(false);
56587
- onClear();
56588
- };
56589
- const handleDeleteCancel = () => {
56590
- setPendingDelete(false);
56591
- };
56592
- const handleClear = () => {
56593
- setPendingDelete(false);
56594
56589
  onClear();
56595
56590
  };
56596
56591
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -56619,25 +56614,15 @@ function DeviceBatchToolbar({ selectedIds, onClear, remove, disable, enable }) {
56619
56614
  className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
56620
56615
  children: t("uiLibrary.batchToolbar.enable", "Enable")
56621
56616
  }),
56622
- pendingDelete ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
56623
- type: "button",
56624
- onClick: handleDeleteConfirm,
56625
- className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-red-500/50 bg-red-500/10 text-red-400 hover:bg-red-500/20"),
56626
- children: t("uiLibrary.batchToolbar.confirmDelete", "Confirm delete {{count}}?", { count })
56627
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
56628
- type: "button",
56629
- onClick: handleDeleteCancel,
56630
- className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
56631
- children: t("uiLibrary.batchToolbar.cancel", "Cancel")
56632
- })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
56617
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
56633
56618
  type: "button",
56634
- onClick: handleDeleteFirst,
56635
- className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-red-500/40 bg-red-500/10 text-red-400 hover:bg-red-500/20"),
56619
+ onClick: () => void handleDelete(),
56620
+ className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-danger/40 bg-danger/10 text-danger hover:bg-danger/20"),
56636
56621
  children: t("uiLibrary.batchToolbar.delete", "Delete")
56637
56622
  }),
56638
56623
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
56639
56624
  type: "button",
56640
- onClick: handleClear,
56625
+ onClick: onClear,
56641
56626
  className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
56642
56627
  children: t("uiLibrary.batchToolbar.clear", "Clear")
56643
56628
  })
@@ -70966,7 +70951,7 @@ var MotionZonesSettings = (0, react$1.lazy)(() => Promise.resolve().then(() => r
70966
70951
  /** Lazy-wrapped `SceneMonitorEditor` — code-split off the main bundle for the
70967
70952
  * same reason: it mounts `MaskShapeCanvas`, which chains react-konva. Render
70968
70953
  * inside `<Suspense>`; the admin-ui `/scenes` dialog is its only caller. */
70969
- var SceneMonitorEditor = (0, react$1.lazy)(() => Promise.resolve().then(() => require("./SceneMonitorEditor-D_jkzQ2o.cjs")).then((m) => ({ default: m.SceneMonitorEditor })));
70954
+ var SceneMonitorEditor = (0, react$1.lazy)(() => Promise.resolve().then(() => require("./SceneMonitorEditor-C-way4ES.cjs")).then((m) => ({ default: m.SceneMonitorEditor })));
70970
70955
  /** Lazy-wrapped `PrivacyMaskSettings` — code-split off the main bundle. */
70971
70956
  var PrivacyMaskSettings = (0, react$1.lazy)(() => Promise.resolve().then(() => require("./PrivacyMaskSettings-BUWvVF_n.cjs")).then((m) => ({ default: m.PrivacyMaskSettings })));
70972
70957
  //#endregion
@@ -72095,6 +72080,7 @@ exports.useClusterTopology = useClusterTopology;
72095
72080
  exports.useColorGetStatus = useColorGetStatus;
72096
72081
  exports.useColorSetColor = useColorSetColor;
72097
72082
  exports.useConfirm = useConfirm;
72083
+ exports.useConfirm$1 = useConfirm;
72098
72084
  exports.useConnectionTestDescribeTest = useConnectionTestDescribeTest;
72099
72085
  exports.useConnectionTestTestSettings = useConnectionTestTestSettings;
72100
72086
  exports.useConnectivityGetStatus = useConnectivityGetStatus;
package/dist/index.js CHANGED
@@ -56542,7 +56542,7 @@ function PrimaryChildPicker({ childrenList, selectedId, onSetPrimary, disabled,
56542
56542
  //#region src/composites/device-list/batch-toolbar.tsx
56543
56543
  function DeviceBatchToolbar({ selectedIds, onClear, remove, disable, enable }) {
56544
56544
  const t = useUiText();
56545
- const [pendingDelete, setPendingDelete] = useState(false);
56545
+ const confirm = useConfirm();
56546
56546
  if (selectedIds.size === 0) return null;
56547
56547
  const ids = [...selectedIds];
56548
56548
  const count = ids.length;
@@ -56554,19 +56554,14 @@ function DeviceBatchToolbar({ selectedIds, onClear, remove, disable, enable }) {
56554
56554
  for (const id of ids) enable({ deviceId: id });
56555
56555
  onClear();
56556
56556
  };
56557
- const handleDeleteFirst = () => {
56558
- setPendingDelete(true);
56559
- };
56560
- const handleDeleteConfirm = () => {
56557
+ const handleDelete = async () => {
56558
+ if (!await confirm({
56559
+ title: t("uiLibrary.batchToolbar.deleteTitle", "Delete {{count}} devices?", { count }),
56560
+ message: t("uiLibrary.batchToolbar.deleteMessage", "{{count}} selected devices will be deleted. This cannot be undone.", { count }),
56561
+ confirmLabel: t("uiLibrary.batchToolbar.delete", "Delete"),
56562
+ variant: "danger"
56563
+ })) return;
56561
56564
  for (const id of ids) remove({ deviceId: id });
56562
- setPendingDelete(false);
56563
- onClear();
56564
- };
56565
- const handleDeleteCancel = () => {
56566
- setPendingDelete(false);
56567
- };
56568
- const handleClear = () => {
56569
- setPendingDelete(false);
56570
56565
  onClear();
56571
56566
  };
56572
56567
  return /* @__PURE__ */ jsxs("div", {
@@ -56595,25 +56590,15 @@ function DeviceBatchToolbar({ selectedIds, onClear, remove, disable, enable }) {
56595
56590
  className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
56596
56591
  children: t("uiLibrary.batchToolbar.enable", "Enable")
56597
56592
  }),
56598
- pendingDelete ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("button", {
56599
- type: "button",
56600
- onClick: handleDeleteConfirm,
56601
- className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-red-500/50 bg-red-500/10 text-red-400 hover:bg-red-500/20"),
56602
- children: t("uiLibrary.batchToolbar.confirmDelete", "Confirm delete {{count}}?", { count })
56603
- }), /* @__PURE__ */ jsx("button", {
56604
- type: "button",
56605
- onClick: handleDeleteCancel,
56606
- className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
56607
- children: t("uiLibrary.batchToolbar.cancel", "Cancel")
56608
- })] }) : /* @__PURE__ */ jsx("button", {
56593
+ /* @__PURE__ */ jsx("button", {
56609
56594
  type: "button",
56610
- onClick: handleDeleteFirst,
56611
- className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-red-500/40 bg-red-500/10 text-red-400 hover:bg-red-500/20"),
56595
+ onClick: () => void handleDelete(),
56596
+ className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-danger/40 bg-danger/10 text-danger hover:bg-danger/20"),
56612
56597
  children: t("uiLibrary.batchToolbar.delete", "Delete")
56613
56598
  }),
56614
56599
  /* @__PURE__ */ jsx("button", {
56615
56600
  type: "button",
56616
- onClick: handleClear,
56601
+ onClick: onClear,
56617
56602
  className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
56618
56603
  children: t("uiLibrary.batchToolbar.clear", "Clear")
56619
56604
  })
@@ -70942,7 +70927,7 @@ var MotionZonesSettings = lazy(() => import("./MotionZonesSettings-BCU2NW7w.js")
70942
70927
  /** Lazy-wrapped `SceneMonitorEditor` — code-split off the main bundle for the
70943
70928
  * same reason: it mounts `MaskShapeCanvas`, which chains react-konva. Render
70944
70929
  * inside `<Suspense>`; the admin-ui `/scenes` dialog is its only caller. */
70945
- var SceneMonitorEditor = lazy(() => import("./SceneMonitorEditor-5N2xqurW.js").then((m) => ({ default: m.SceneMonitorEditor })));
70930
+ var SceneMonitorEditor = lazy(() => import("./SceneMonitorEditor-BgqJYVdM.js").then((m) => ({ default: m.SceneMonitorEditor })));
70946
70931
  /** Lazy-wrapped `PrivacyMaskSettings` — code-split off the main bundle. */
70947
70932
  var PrivacyMaskSettings = lazy(() => import("./PrivacyMaskSettings-VntwpB1u.js").then((m) => ({ default: m.PrivacyMaskSettings })));
70948
70933
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/ui-library",
3
- "version": "1.2.335",
3
+ "version": "1.2.337",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/camstack/server.git",
@@ -40,7 +40,9 @@
40
40
  "files": [
41
41
  "dist",
42
42
  "src/tailwind",
43
- "assets"
43
+ "assets",
44
+ "README.md",
45
+ "CHANGELOG.md"
44
46
  ],
45
47
  "scripts": {
46
48
  "build": "vite build && npx tsx ../../scripts/emit-isolated-dts.ts --root . --src src --out dist",