@camstack/ui-library 1.2.19 → 1.2.21

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.
@@ -5,6 +5,19 @@ import { DeviceControlProps } from './control-registry';
5
5
  * no glyph, no state label, no card padding. Cap-gated on `alarm-panel`.
6
6
  * Drops into a 200 px control column without towering over sibling rows.
7
7
  */
8
+ /**
9
+ * The row/card-sized alarm control.
10
+ *
11
+ * It leads with the STATE, and that is the fix rather than the decoration: the
12
+ * first version rendered only the segment strip, so the sole indication that a
13
+ * panel was armed was which of four buttons happened to be highlighted — and in
14
+ * a grid card the strip overflowed and cut the highlighted one off entirely. A
15
+ * security control whose state you have to infer from a button is not showing
16
+ * you its state.
17
+ *
18
+ * The strip is allowed to WRAP for the same reason: four modes do not fit a
19
+ * card at phone width, and a truncated alarm control is worse than a tall one.
20
+ */
8
21
  export declare function AlarmInlineControl({ trpc, deviceId, optimistic }: DeviceControlProps): ReactNode;
9
22
  /**
10
23
  * AlarmHeroCard — segmented arm/disarm hero for AlarmPanel devices.
package/dist/index.cjs CHANGED
@@ -26752,25 +26752,25 @@ function isArmedState(state) {
26752
26752
  function isTransitioningState(state) {
26753
26753
  return state === "arming" || state === "disarming";
26754
26754
  }
26755
- function AlarmGlyph({ icon }) {
26755
+ function AlarmGlyph({ icon, sizeClass = "h-8 w-8" }) {
26756
26756
  if (icon === "disarmed") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShieldOff, {
26757
- className: "h-8 w-8",
26757
+ className: sizeClass,
26758
26758
  "aria-hidden": "true"
26759
26759
  });
26760
26760
  if (icon === "triggered") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShieldAlert, {
26761
- className: "h-8 w-8",
26761
+ className: sizeClass,
26762
26762
  "aria-hidden": "true"
26763
26763
  });
26764
26764
  if (icon === "pending") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Shield, {
26765
- className: "h-8 w-8",
26765
+ className: sizeClass,
26766
26766
  "aria-hidden": "true"
26767
26767
  });
26768
26768
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShieldCheck, {
26769
- className: "h-8 w-8",
26769
+ className: sizeClass,
26770
26770
  "aria-hidden": "true"
26771
26771
  });
26772
26772
  }
26773
- function Segment({ label, active, busy, onSelect }) {
26773
+ function Segment({ label, active, busy, onSelect, compact = false }) {
26774
26774
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
26775
26775
  type: "button",
26776
26776
  "aria-label": label,
@@ -26780,7 +26780,7 @@ function Segment({ label, active, busy, onSelect }) {
26780
26780
  e.stopPropagation();
26781
26781
  if (!busy) onSelect();
26782
26782
  },
26783
- className: cn("flex-1 rounded-md px-3 py-2 text-sm font-medium transition-colors", active ? "bg-primary text-primary-foreground shadow-sm" : "text-foreground-muted hover:bg-surface-hover", busy && "opacity-40 cursor-not-allowed"),
26783
+ className: cn("rounded-md font-medium transition-colors", compact ? "px-2 py-1 text-xs" : "flex-1 px-3 py-2 text-sm", active ? "bg-primary text-primary-foreground shadow-sm" : "text-foreground-muted hover:bg-surface-hover", busy && "opacity-40 cursor-not-allowed"),
26784
26784
  children: label
26785
26785
  });
26786
26786
  }
@@ -26790,21 +26790,23 @@ function Segment({ label, active, busy, onSelect }) {
26790
26790
  * segment row. Transitioning states (arming/disarming) leave NO segment
26791
26791
  * highlighted so the Disarm button does not look active mid-arm.
26792
26792
  */
26793
- function AlarmSegments({ state, availableModes, busy, onDisarm, onArm }) {
26793
+ function AlarmSegments({ state, availableModes, busy, onDisarm, onArm, compact = false }) {
26794
26794
  const armed = isArmedState(state);
26795
26795
  const transitioning = isTransitioningState(state);
26796
26796
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
26797
- className: "flex items-stretch gap-1 rounded-lg border border-border bg-surface-subtle p-1",
26797
+ className: cn("flex min-w-0 flex-wrap items-stretch gap-1 rounded-lg border border-border bg-surface-subtle", compact ? "p-0.5" : "p-1"),
26798
26798
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Segment, {
26799
26799
  label: "Disarm",
26800
26800
  active: !armed && !transitioning,
26801
26801
  busy,
26802
- onSelect: onDisarm
26802
+ onSelect: onDisarm,
26803
+ compact
26803
26804
  }), availableModes.map((mode) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Segment, {
26804
26805
  label: ARM_MODE_LABEL[mode],
26805
26806
  active: !transitioning && state === `armed_${mode}`,
26806
26807
  busy,
26807
- onSelect: () => onArm(mode)
26808
+ onSelect: () => onArm(mode),
26809
+ compact
26808
26810
  }, mode))]
26809
26811
  });
26810
26812
  }
@@ -26845,16 +26847,40 @@ function useAlarmControl(trpc, deviceId, optimistic) {
26845
26847
  * no glyph, no state label, no card padding. Cap-gated on `alarm-panel`.
26846
26848
  * Drops into a 200 px control column without towering over sibling rows.
26847
26849
  */
26850
+ /**
26851
+ * The row/card-sized alarm control.
26852
+ *
26853
+ * It leads with the STATE, and that is the fix rather than the decoration: the
26854
+ * first version rendered only the segment strip, so the sole indication that a
26855
+ * panel was armed was which of four buttons happened to be highlighted — and in
26856
+ * a grid card the strip overflowed and cut the highlighted one off entirely. A
26857
+ * security control whose state you have to infer from a button is not showing
26858
+ * you its state.
26859
+ *
26860
+ * The strip is allowed to WRAP for the same reason: four modes do not fit a
26861
+ * card at phone width, and a truncated alarm control is worse than a tall one.
26862
+ */
26848
26863
  function AlarmInlineControl({ trpc, deviceId, optimistic }) {
26849
26864
  const { display, capPresent, busy, handleDisarm, handleArm } = useAlarmControl(trpc, deviceId, optimistic);
26850
26865
  if (!capPresent) return null;
26851
26866
  if (display === void 0) return null;
26852
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AlarmSegments, {
26853
- state: display.state,
26854
- availableModes: display.availableModes,
26855
- busy,
26856
- onDisarm: handleDisarm,
26857
- onArm: handleArm
26867
+ const config = ALARM_STATE_CONFIG[display.state];
26868
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
26869
+ className: "flex min-w-0 flex-wrap items-center gap-2",
26870
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
26871
+ className: cn("inline-flex shrink-0 items-center gap-1.5 rounded-md border px-2 py-1 text-[11px] font-medium", config.colorClass),
26872
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AlarmGlyph, {
26873
+ icon: config.icon,
26874
+ sizeClass: "h-3.5 w-3.5"
26875
+ }), config.label]
26876
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AlarmSegments, {
26877
+ state: display.state,
26878
+ availableModes: display.availableModes,
26879
+ busy,
26880
+ onDisarm: handleDisarm,
26881
+ onArm: handleArm,
26882
+ compact: true
26883
+ })]
26858
26884
  });
26859
26885
  }
26860
26886
  /**
@@ -26884,10 +26910,10 @@ function AlarmHeroCard({ trpc, deviceId, optimistic }) {
26884
26910
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
26885
26911
  "data-testid": "alarm-state",
26886
26912
  "data-state": display.state,
26887
- className: cn("flex h-16 w-16 items-center justify-center rounded-full border-2", config.colorClass),
26913
+ className: cn("flex h-16 w-16 items-center justify-center rounded-full border-2", config.colorClass, isTransitioningState(display.state) && "animate-pulse"),
26888
26914
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AlarmGlyph, { icon: config.icon })
26889
26915
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
26890
- className: "text-sm font-medium text-foreground-muted",
26916
+ className: cn("text-sm font-medium", config.colorClass, "border-0 bg-transparent"),
26891
26917
  children: config.label
26892
26918
  })]
26893
26919
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AlarmSegments, {
package/dist/index.js CHANGED
@@ -26728,25 +26728,25 @@ function isArmedState(state) {
26728
26728
  function isTransitioningState(state) {
26729
26729
  return state === "arming" || state === "disarming";
26730
26730
  }
26731
- function AlarmGlyph({ icon }) {
26731
+ function AlarmGlyph({ icon, sizeClass = "h-8 w-8" }) {
26732
26732
  if (icon === "disarmed") return /* @__PURE__ */ jsx(ShieldOff, {
26733
- className: "h-8 w-8",
26733
+ className: sizeClass,
26734
26734
  "aria-hidden": "true"
26735
26735
  });
26736
26736
  if (icon === "triggered") return /* @__PURE__ */ jsx(ShieldAlert, {
26737
- className: "h-8 w-8",
26737
+ className: sizeClass,
26738
26738
  "aria-hidden": "true"
26739
26739
  });
26740
26740
  if (icon === "pending") return /* @__PURE__ */ jsx(Shield, {
26741
- className: "h-8 w-8",
26741
+ className: sizeClass,
26742
26742
  "aria-hidden": "true"
26743
26743
  });
26744
26744
  return /* @__PURE__ */ jsx(ShieldCheck, {
26745
- className: "h-8 w-8",
26745
+ className: sizeClass,
26746
26746
  "aria-hidden": "true"
26747
26747
  });
26748
26748
  }
26749
- function Segment({ label, active, busy, onSelect }) {
26749
+ function Segment({ label, active, busy, onSelect, compact = false }) {
26750
26750
  return /* @__PURE__ */ jsx("button", {
26751
26751
  type: "button",
26752
26752
  "aria-label": label,
@@ -26756,7 +26756,7 @@ function Segment({ label, active, busy, onSelect }) {
26756
26756
  e.stopPropagation();
26757
26757
  if (!busy) onSelect();
26758
26758
  },
26759
- className: cn("flex-1 rounded-md px-3 py-2 text-sm font-medium transition-colors", active ? "bg-primary text-primary-foreground shadow-sm" : "text-foreground-muted hover:bg-surface-hover", busy && "opacity-40 cursor-not-allowed"),
26759
+ className: cn("rounded-md font-medium transition-colors", compact ? "px-2 py-1 text-xs" : "flex-1 px-3 py-2 text-sm", active ? "bg-primary text-primary-foreground shadow-sm" : "text-foreground-muted hover:bg-surface-hover", busy && "opacity-40 cursor-not-allowed"),
26760
26760
  children: label
26761
26761
  });
26762
26762
  }
@@ -26766,21 +26766,23 @@ function Segment({ label, active, busy, onSelect }) {
26766
26766
  * segment row. Transitioning states (arming/disarming) leave NO segment
26767
26767
  * highlighted so the Disarm button does not look active mid-arm.
26768
26768
  */
26769
- function AlarmSegments({ state, availableModes, busy, onDisarm, onArm }) {
26769
+ function AlarmSegments({ state, availableModes, busy, onDisarm, onArm, compact = false }) {
26770
26770
  const armed = isArmedState(state);
26771
26771
  const transitioning = isTransitioningState(state);
26772
26772
  return /* @__PURE__ */ jsxs("div", {
26773
- className: "flex items-stretch gap-1 rounded-lg border border-border bg-surface-subtle p-1",
26773
+ className: cn("flex min-w-0 flex-wrap items-stretch gap-1 rounded-lg border border-border bg-surface-subtle", compact ? "p-0.5" : "p-1"),
26774
26774
  children: [/* @__PURE__ */ jsx(Segment, {
26775
26775
  label: "Disarm",
26776
26776
  active: !armed && !transitioning,
26777
26777
  busy,
26778
- onSelect: onDisarm
26778
+ onSelect: onDisarm,
26779
+ compact
26779
26780
  }), availableModes.map((mode) => /* @__PURE__ */ jsx(Segment, {
26780
26781
  label: ARM_MODE_LABEL[mode],
26781
26782
  active: !transitioning && state === `armed_${mode}`,
26782
26783
  busy,
26783
- onSelect: () => onArm(mode)
26784
+ onSelect: () => onArm(mode),
26785
+ compact
26784
26786
  }, mode))]
26785
26787
  });
26786
26788
  }
@@ -26821,16 +26823,40 @@ function useAlarmControl(trpc, deviceId, optimistic) {
26821
26823
  * no glyph, no state label, no card padding. Cap-gated on `alarm-panel`.
26822
26824
  * Drops into a 200 px control column without towering over sibling rows.
26823
26825
  */
26826
+ /**
26827
+ * The row/card-sized alarm control.
26828
+ *
26829
+ * It leads with the STATE, and that is the fix rather than the decoration: the
26830
+ * first version rendered only the segment strip, so the sole indication that a
26831
+ * panel was armed was which of four buttons happened to be highlighted — and in
26832
+ * a grid card the strip overflowed and cut the highlighted one off entirely. A
26833
+ * security control whose state you have to infer from a button is not showing
26834
+ * you its state.
26835
+ *
26836
+ * The strip is allowed to WRAP for the same reason: four modes do not fit a
26837
+ * card at phone width, and a truncated alarm control is worse than a tall one.
26838
+ */
26824
26839
  function AlarmInlineControl({ trpc, deviceId, optimistic }) {
26825
26840
  const { display, capPresent, busy, handleDisarm, handleArm } = useAlarmControl(trpc, deviceId, optimistic);
26826
26841
  if (!capPresent) return null;
26827
26842
  if (display === void 0) return null;
26828
- return /* @__PURE__ */ jsx(AlarmSegments, {
26829
- state: display.state,
26830
- availableModes: display.availableModes,
26831
- busy,
26832
- onDisarm: handleDisarm,
26833
- onArm: handleArm
26843
+ const config = ALARM_STATE_CONFIG[display.state];
26844
+ return /* @__PURE__ */ jsxs("div", {
26845
+ className: "flex min-w-0 flex-wrap items-center gap-2",
26846
+ children: [/* @__PURE__ */ jsxs("span", {
26847
+ className: cn("inline-flex shrink-0 items-center gap-1.5 rounded-md border px-2 py-1 text-[11px] font-medium", config.colorClass),
26848
+ children: [/* @__PURE__ */ jsx(AlarmGlyph, {
26849
+ icon: config.icon,
26850
+ sizeClass: "h-3.5 w-3.5"
26851
+ }), config.label]
26852
+ }), /* @__PURE__ */ jsx(AlarmSegments, {
26853
+ state: display.state,
26854
+ availableModes: display.availableModes,
26855
+ busy,
26856
+ onDisarm: handleDisarm,
26857
+ onArm: handleArm,
26858
+ compact: true
26859
+ })]
26834
26860
  });
26835
26861
  }
26836
26862
  /**
@@ -26860,10 +26886,10 @@ function AlarmHeroCard({ trpc, deviceId, optimistic }) {
26860
26886
  children: [/* @__PURE__ */ jsx("div", {
26861
26887
  "data-testid": "alarm-state",
26862
26888
  "data-state": display.state,
26863
- className: cn("flex h-16 w-16 items-center justify-center rounded-full border-2", config.colorClass),
26889
+ className: cn("flex h-16 w-16 items-center justify-center rounded-full border-2", config.colorClass, isTransitioningState(display.state) && "animate-pulse"),
26864
26890
  children: /* @__PURE__ */ jsx(AlarmGlyph, { icon: config.icon })
26865
26891
  }), /* @__PURE__ */ jsx("span", {
26866
- className: "text-sm font-medium text-foreground-muted",
26892
+ className: cn("text-sm font-medium", config.colorClass, "border-0 bg-transparent"),
26867
26893
  children: config.label
26868
26894
  })]
26869
26895
  }), /* @__PURE__ */ jsx(AlarmSegments, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/ui-library",
3
- "version": "1.2.19",
3
+ "version": "1.2.21",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",