@djangocfg/ui-tools 2.1.350 → 2.1.351

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-tools",
3
- "version": "2.1.350",
3
+ "version": "2.1.351",
4
4
  "description": "Heavy React tools with lazy loading - for Electron, Vite, CRA, Next.js apps",
5
5
  "keywords": [
6
6
  "ui-tools",
@@ -101,8 +101,8 @@
101
101
  "check": "tsc --noEmit"
102
102
  },
103
103
  "peerDependencies": {
104
- "@djangocfg/i18n": "^2.1.350",
105
- "@djangocfg/ui-core": "^2.1.350",
104
+ "@djangocfg/i18n": "^2.1.351",
105
+ "@djangocfg/ui-core": "^2.1.351",
106
106
  "consola": "^3.4.2",
107
107
  "lodash-es": "^4.18.1",
108
108
  "lucide-react": "^0.545.0",
@@ -155,10 +155,10 @@
155
155
  "material-file-icons": "^2.4.0"
156
156
  },
157
157
  "devDependencies": {
158
- "@djangocfg/i18n": "^2.1.350",
158
+ "@djangocfg/i18n": "^2.1.351",
159
159
  "@djangocfg/playground": "workspace:*",
160
- "@djangocfg/typescript-config": "^2.1.350",
161
- "@djangocfg/ui-core": "^2.1.350",
160
+ "@djangocfg/typescript-config": "^2.1.351",
161
+ "@djangocfg/ui-core": "^2.1.351",
162
162
  "@types/lodash-es": "^4.17.12",
163
163
  "@types/mapbox__mapbox-gl-draw": "^1.4.8",
164
164
  "@types/node": "^24.7.2",
@@ -0,0 +1,78 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Mute / unmute toggle for chat audio events.
5
+ *
6
+ * Reads the current ``muted`` state from the active chat context and
7
+ * persists changes through ``useChatAudioPrefs`` (cross-tab safe). The
8
+ * button auto-hides when no ``audio.sounds`` config is provided —
9
+ * showing a mute toggle for a chat with no sounds is just clutter.
10
+ *
11
+ * Drop into a ChatRoot ``header`` slot or anywhere inside a chat
12
+ * provider:
13
+ *
14
+ * ```tsx
15
+ * <ChatRoot
16
+ * header={<AudioToggle />}
17
+ * audio={{ sounds: { messageReceived: '/ping.mp3' } }}
18
+ * ...
19
+ * />
20
+ * ```
21
+ */
22
+
23
+ import { Volume2, VolumeX } from 'lucide-react';
24
+
25
+ import { Button } from '@djangocfg/ui-core/components';
26
+ import { cn } from '@djangocfg/ui-core/lib';
27
+
28
+ import { useChatContextOptional } from '../context';
29
+ import { useChatAudioPrefs } from '../core/audio/preferences';
30
+
31
+ export interface AudioToggleProps {
32
+ /** Visual size — matches Button sizes. Default: ``icon``. */
33
+ size?: 'sm' | 'icon';
34
+ /** Variant passed to the underlying Button. Default: ``ghost``. */
35
+ variant?: 'ghost' | 'outline' | 'secondary';
36
+ /** Force-show even when no audio config is wired (e.g. for stories). */
37
+ alwaysShow?: boolean;
38
+ className?: string;
39
+ }
40
+
41
+ export function AudioToggle({
42
+ size = 'icon',
43
+ variant = 'ghost',
44
+ alwaysShow = false,
45
+ className,
46
+ }: AudioToggleProps) {
47
+ // Read straight from the persist store so the toggle works even
48
+ // when rendered OUTSIDE the ChatRoot (e.g. in a parent header). The
49
+ // chat audio bus reads the same store, so a click here flips the
50
+ // "muted" state for any sibling ChatRoot in the same tab and
51
+ // mirrors across tabs via the storage event.
52
+ const muted = useChatAudioPrefs((s) => s.muted);
53
+ const setMuted = useChatAudioPrefs((s) => s.setMuted);
54
+
55
+ // If a ChatRoot is in scope, hide unless it actually wired sounds —
56
+ // otherwise the button is a no-op for that surface. When rendered
57
+ // standalone (no context), default to visible.
58
+ const ctx = useChatContextOptional();
59
+ if (ctx && !ctx.hasAudio && !alwaysShow) return null;
60
+
61
+ const Icon = muted ? VolumeX : Volume2;
62
+ const label = muted ? 'Unmute chat sounds' : 'Mute chat sounds';
63
+
64
+ return (
65
+ <Button
66
+ type="button"
67
+ variant={variant}
68
+ size={size}
69
+ onClick={() => setMuted(!muted)}
70
+ aria-label={label}
71
+ aria-pressed={muted}
72
+ title={label}
73
+ className={cn(size === 'icon' ? 'h-9 w-9' : '', className)}
74
+ >
75
+ <Icon aria-hidden className="size-4" />
76
+ </Button>
77
+ );
78
+ }
@@ -22,3 +22,4 @@ export { EmptyState, type EmptyStateProps } from './EmptyState';
22
22
  export { ErrorBanner, type ErrorBannerProps } from './ErrorBanner';
23
23
  export { JumpToLatest, type JumpToLatestProps } from './JumpToLatest';
24
24
  export { StreamingIndicator, type StreamingIndicatorProps } from './StreamingIndicator';
25
+ export { AudioToggle, type AudioToggleProps } from './AudioToggle';
@@ -22,6 +22,10 @@ export interface ChatContextValue extends UseChatReturn {
22
22
  config: ChatConfig;
23
23
  labels: ChatLabels;
24
24
  audio: UseChatAudioReturn;
25
+ /** True iff the host wired at least one ``audio.sounds[event]`` URL.
26
+ * Components like ``AudioToggle`` use this to auto-hide when there
27
+ * is nothing to mute. */
28
+ hasAudio: boolean;
25
29
  }
26
30
 
27
31
  const Ctx = createContext<ChatContextValue | null>(null);
@@ -101,9 +105,17 @@ export function ChatProvider({
101
105
  [config.labels],
102
106
  );
103
107
 
108
+ const hasAudio = useMemo<boolean>(() => {
109
+ const sounds = audio?.sounds;
110
+ if (!sounds) return false;
111
+ return Object.values(sounds).some(
112
+ (v) => typeof v === 'string' && v.length > 0,
113
+ );
114
+ }, [audio]);
115
+
104
116
  const value = useMemo<ChatContextValue>(
105
- () => ({ ...chat, layout, config, labels, audio: audioApi }),
106
- [chat, layout, config, labels, audioApi],
117
+ () => ({ ...chat, layout, config, labels, audio: audioApi, hasAudio }),
118
+ [chat, layout, config, labels, audioApi, hasAudio],
107
119
  );
108
120
 
109
121
  return (
@@ -137,6 +137,7 @@ export {
137
137
  ErrorBanner,
138
138
  JumpToLatest,
139
139
  StreamingIndicator,
140
+ AudioToggle,
140
141
  type ChatRootProps,
141
142
  type MessageListProps,
142
143
  type MessageBubbleProps,
@@ -155,6 +156,7 @@ export {
155
156
  type ErrorBannerProps,
156
157
  type JumpToLatestProps,
157
158
  type StreamingIndicatorProps,
159
+ type AudioToggleProps,
158
160
  } from './components';
159
161
 
160
162
  // Lazy preset
@@ -1,5 +0,0 @@
1
- export { ChatRoot } from './chunk-XG3XAO7W.mjs';
2
- import './chunk-2ZLKZ5VR.mjs';
3
- import './chunk-N2XQF2OL.mjs';
4
- //# sourceMappingURL=ChatRoot-3FZ6DXBD.mjs.map
5
- //# sourceMappingURL=ChatRoot-3FZ6DXBD.mjs.map
@@ -1,14 +0,0 @@
1
- 'use strict';
2
-
3
- var chunkHVIL2FEQ_cjs = require('./chunk-HVIL2FEQ.cjs');
4
- require('./chunk-B5AWZOHJ.cjs');
5
- require('./chunk-OLISEQHS.cjs');
6
-
7
-
8
-
9
- Object.defineProperty(exports, "ChatRoot", {
10
- enumerable: true,
11
- get: function () { return chunkHVIL2FEQ_cjs.ChatRoot; }
12
- });
13
- //# sourceMappingURL=ChatRoot-7TTJRYBA.cjs.map
14
- //# sourceMappingURL=ChatRoot-7TTJRYBA.cjs.map