@nordmanai/nif 0.2.20 → 0.2.22
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/dist/index.js +6453 -6454
- package/dist/styles.css +1 -1
- package/dist/ui/config/nm-composer.d.ts +0 -2
- package/package.json +1 -1
- package/registry/index.json +7 -2
- package/registry/ui/components/nm-composer/nm-composer.css +1 -50
- package/registry/ui/components/nm-composer/nm-composer.tsx +31 -29
- package/registry/ui/components/nm-sidebar/nm-sidebar.css +4 -0
- package/registry/ui/config/nm-composer.ts +0 -2
|
@@ -34,8 +34,6 @@ export declare const NM_COMPOSER_CLASSES: {
|
|
|
34
34
|
readonly bar: 'nm-composer__bar';
|
|
35
35
|
readonly start: 'nm-composer__start';
|
|
36
36
|
readonly end: 'nm-composer__end';
|
|
37
|
-
readonly button: 'nm-composer__button';
|
|
38
|
-
readonly primary: 'nm-composer__button--primary';
|
|
39
37
|
readonly stopMark: 'nm-composer__stop-mark';
|
|
40
38
|
};
|
|
41
39
|
/** Set on the root with a shape value; every button in the bar, own or hosted, takes that shape. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nordmanai/nif",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.22",
|
|
4
4
|
"description": "Nordman Interface Framework: the one UI and UX system for every Nordman product. Base UI, in the shadcn composition, styled by NIF tokens.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
package/registry/index.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.2.
|
|
2
|
+
"version": "0.2.21",
|
|
3
3
|
"versions": {
|
|
4
4
|
"@animate-ui/react": "^2.4.2",
|
|
5
5
|
"@base-ui/react": "^1.9.0",
|
|
@@ -2313,13 +2313,17 @@
|
|
|
2313
2313
|
"components/nm-voice-orb/nm-voice-orb.css",
|
|
2314
2314
|
"components/nm-voice-orb/nm-voice-orb.tsx",
|
|
2315
2315
|
"components/nm-voice-orb/use-nm-voice-orb-theme.ts",
|
|
2316
|
+
"components/nordman-icon-button/nordman-icon-button.css",
|
|
2317
|
+
"components/nordman-icon-button/nordman-icon-button.tsx",
|
|
2316
2318
|
"config/nm-appearance.ts",
|
|
2317
2319
|
"config/nm-composer.ts",
|
|
2320
|
+
"config/nm-icon-button.ts",
|
|
2318
2321
|
"config/nm-icons-extended.ts",
|
|
2319
2322
|
"config/nm-icons.ts",
|
|
2320
2323
|
"config/nm-shape.ts",
|
|
2321
2324
|
"config/nm-site-palette.ts",
|
|
2322
2325
|
"config/nm-voice-orb.ts",
|
|
2326
|
+
"config/nordman-chrome.ts",
|
|
2323
2327
|
"kit-logger.ts",
|
|
2324
2328
|
"lib/appearance/cloudOrbAppearance.ts",
|
|
2325
2329
|
"lib/appearance/color.ts",
|
|
@@ -2337,7 +2341,8 @@
|
|
|
2337
2341
|
"uses": [
|
|
2338
2342
|
"chip",
|
|
2339
2343
|
"icon",
|
|
2340
|
-
"voice-orb"
|
|
2344
|
+
"voice-orb",
|
|
2345
|
+
"nordman-icon-button"
|
|
2341
2346
|
]
|
|
2342
2347
|
},
|
|
2343
2348
|
"context-menu": {
|
|
@@ -155,60 +155,11 @@
|
|
|
155
155
|
gap: var(--nm-space-8);
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
/*
|
|
159
|
-
.nm-composer__button {
|
|
160
|
-
display: inline-flex;
|
|
161
|
-
flex-shrink: 0;
|
|
162
|
-
align-items: center;
|
|
163
|
-
justify-content: center;
|
|
164
|
-
box-sizing: content-box;
|
|
165
|
-
width: var(--nm-icon-button);
|
|
166
|
-
height: var(--nm-icon-button);
|
|
167
|
-
padding: calc(var(--nm-space-6) / 2);
|
|
168
|
-
border: 0;
|
|
169
|
-
border-radius: var(--nm-control-r);
|
|
170
|
-
background: transparent;
|
|
171
|
-
color: var(--nm-fg-muted);
|
|
172
|
-
cursor: pointer;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
.nm-composer__button .nm-icon {
|
|
176
|
-
width: 100%;
|
|
177
|
-
height: 100%;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
.nm-composer__button:hover:not(:disabled),
|
|
181
|
-
.nm-composer__button:focus-visible,
|
|
182
|
-
.nm-composer__button[aria-pressed='true'] {
|
|
183
|
-
background: var(--nm-inset);
|
|
184
|
-
color: var(--nm-fg);
|
|
185
|
-
outline: none;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/* Send, Stop, Done and the voice button are the primary action: they take the theme accent. */
|
|
189
|
-
.nm-composer__button--primary {
|
|
190
|
-
background: var(--nm-accent);
|
|
191
|
-
color: var(--nm-canvas);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
.nm-composer__button--primary:hover:not(:disabled),
|
|
195
|
-
.nm-composer__button--primary:focus-visible {
|
|
196
|
-
background: var(--nm-accent);
|
|
197
|
-
color: var(--nm-canvas);
|
|
198
|
-
opacity: 0.88;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/* Round shape: every button in the bar, the composer's own and any hosted icon button, is a circle. */
|
|
202
|
-
.nm-composer[data-button-shape='round'] .nm-composer__button,
|
|
158
|
+
/* Round shape: any icon button hosted in the bar is a circle. */
|
|
203
159
|
.nm-composer[data-button-shape='round'] .nm-icon-button {
|
|
204
160
|
border-radius: var(--nm-radius-round);
|
|
205
161
|
}
|
|
206
162
|
|
|
207
|
-
.nm-composer__button:disabled {
|
|
208
|
-
cursor: default;
|
|
209
|
-
opacity: 0.4;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
163
|
.nm-composer__stop-mark {
|
|
213
164
|
display: block;
|
|
214
165
|
width: 60%;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NmChip } from '../nm-chip/nm-chip.tsx';
|
|
2
|
-
import { useCallback, useEffect, useLayoutEffect, useRef, useState, type KeyboardEvent, type ReactNode, type Ref, type RefObject, type TextareaHTMLAttributes } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState, type ComponentProps, type KeyboardEvent, type ReactNode, type Ref, type RefObject, type TextareaHTMLAttributes } from 'react';
|
|
3
3
|
import {
|
|
4
4
|
NM_COMPOSER_CLASSES as C,
|
|
5
5
|
NM_COMPOSER_COPY as COPY,
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
} from '../../config/nm-composer.ts';
|
|
11
11
|
import type { NmControlShape } from '../../config/nm-shape.ts';
|
|
12
12
|
import { NmIcon } from '../nm-icon/nm-icon.tsx';
|
|
13
|
+
import { NordmanIconButton } from '../nordman-icon-button/nordman-icon-button.tsx';
|
|
14
|
+
import { NM_CHATBOX_ACTION_CLASS, NM_ICON_BUTTON_TONES } from '../../config/nm-icon-button.ts';
|
|
13
15
|
import type { NmIconId } from '../../config/nm-icons.ts';
|
|
14
16
|
import { NmVoiceOrb } from '../nm-voice-orb/nm-voice-orb.tsx';
|
|
15
17
|
import type { OrbAdapter } from 'orb-ui';
|
|
@@ -183,6 +185,22 @@ function NmComposerInline(props: { text: string; field: RefObject<HTMLTextAreaEl
|
|
|
183
185
|
);
|
|
184
186
|
}
|
|
185
187
|
|
|
188
|
+
type ComposerButtonProps = Omit<ComponentProps<typeof NordmanIconButton>, 'tone' | 'className'> & {
|
|
189
|
+
/** The primary action takes the theme accent, as the chatbox send does. */
|
|
190
|
+
primary?: boolean;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/** Every button in the bar is the NIF icon button, so it has the same size, padding and states as everywhere else. */
|
|
194
|
+
function ComposerButton({ primary = false, ...props }: ComposerButtonProps) {
|
|
195
|
+
return (
|
|
196
|
+
<NordmanIconButton
|
|
197
|
+
{...props}
|
|
198
|
+
tone={primary ? NM_ICON_BUTTON_TONES.invert : NM_ICON_BUTTON_TONES.quiet}
|
|
199
|
+
className={primary ? NM_CHATBOX_ACTION_CLASS : undefined}
|
|
200
|
+
/>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
186
204
|
export function NmComposer(props: NmComposerProps): ReactNode {
|
|
187
205
|
const ownField = useRef<HTMLTextAreaElement>(null);
|
|
188
206
|
const fieldNode = useRef<HTMLTextAreaElement | null>(null);
|
|
@@ -303,19 +321,13 @@ export function NmComposer(props: NmComposerProps): ReactNode {
|
|
|
303
321
|
if (props.listening) {
|
|
304
322
|
action = (
|
|
305
323
|
<>
|
|
306
|
-
<
|
|
307
|
-
|
|
308
|
-
</button>
|
|
309
|
-
<button type="button" className={`${C.button} ${C.primary}`} aria-label={COPY.done} data-testid="dictation-confirm" data-loading={props.confirming ? 'true' : undefined} disabled={props.disabled || props.confirming} onClick={props.onConfirmListening}>
|
|
310
|
-
<NmIcon id={props.confirming ? 'loader' : 'check'} />
|
|
311
|
-
</button>
|
|
324
|
+
<ComposerButton glyph="x" label={COPY.cancel} testId="dictation-cancel" shape={props.buttonShape} disabled={props.disabled} onClick={props.onCancelListening} />
|
|
325
|
+
<ComposerButton primary glyph={props.confirming ? 'loader' : 'check'} label={COPY.done} testId="dictation-confirm" shape={props.buttonShape} busy={props.confirming} disabled={props.disabled || props.confirming} onClick={props.onConfirmListening} />
|
|
312
326
|
</>
|
|
313
327
|
);
|
|
314
328
|
} else if (props.voiceActive && !canSend) {
|
|
315
329
|
action = props.voiceState === 'connecting' ? (
|
|
316
|
-
<
|
|
317
|
-
<NmIcon id="loader" />
|
|
318
|
-
</button>
|
|
330
|
+
<ComposerButton glyph="loader" label={COPY.voiceEnd} testId="voice-orb-connecting" shape={props.buttonShape} onClick={props.onVoiceStop} />
|
|
319
331
|
) : (
|
|
320
332
|
<NmVoiceOrb
|
|
321
333
|
adapter={props.voiceAdapter}
|
|
@@ -329,21 +341,17 @@ export function NmComposer(props: NmComposerProps): ReactNode {
|
|
|
329
341
|
);
|
|
330
342
|
} else if (props.busy) {
|
|
331
343
|
action = (
|
|
332
|
-
<
|
|
344
|
+
<ComposerButton primary label={COPY.stop} testId="stop-button" shape={props.buttonShape} onClick={props.onStop}>
|
|
333
345
|
<span className={C.stopMark} />
|
|
334
|
-
</
|
|
346
|
+
</ComposerButton>
|
|
335
347
|
);
|
|
336
348
|
} else if (canSend || props.onVoice == null) {
|
|
337
349
|
action = (
|
|
338
|
-
<
|
|
339
|
-
<NmIcon id="send" />
|
|
340
|
-
</button>
|
|
350
|
+
<ComposerButton primary type="submit" glyph="send" label={COPY.send} testId="send-button" shape={props.buttonShape} disabled={props.disabled || !canSend} />
|
|
341
351
|
);
|
|
342
352
|
} else {
|
|
343
353
|
action = (
|
|
344
|
-
<
|
|
345
|
-
<NmIcon id="audio-lines" />
|
|
346
|
-
</button>
|
|
354
|
+
<ComposerButton primary glyph="audio-lines" label={COPY.voice} testId="voice-button" shape={props.buttonShape} disabled={props.disabled} onClick={props.onVoice} />
|
|
347
355
|
);
|
|
348
356
|
}
|
|
349
357
|
|
|
@@ -373,9 +381,9 @@ export function NmComposer(props: NmComposerProps): ReactNode {
|
|
|
373
381
|
<span className={C.queuedText}>{q.text}</span>
|
|
374
382
|
<span className={C.queuedActions}>
|
|
375
383
|
{props.onEditQueued ? (
|
|
376
|
-
<
|
|
384
|
+
<ComposerButton glyph="pen" label={COPY.editQueued} shape={props.buttonShape} onClick={() => props.onEditQueued?.(q.id)} />
|
|
377
385
|
) : null}
|
|
378
|
-
<
|
|
386
|
+
<ComposerButton glyph="x" label={COPY.removeQueued} shape={props.buttonShape} onClick={() => props.onRemoveQueued?.(q.id)} />
|
|
379
387
|
</span>
|
|
380
388
|
</li>
|
|
381
389
|
))}
|
|
@@ -418,21 +426,15 @@ export function NmComposer(props: NmComposerProps): ReactNode {
|
|
|
418
426
|
<div className={C.bar}>
|
|
419
427
|
<div className={C.start}>
|
|
420
428
|
{props.listening ? null : props.addControl ?? (props.onAdd ? (
|
|
421
|
-
<
|
|
422
|
-
<NmIcon id="plus" />
|
|
423
|
-
</button>
|
|
429
|
+
<ComposerButton glyph="plus" label={COPY.attach} shape={props.buttonShape} disabled={props.disabled} onClick={props.onAdd} />
|
|
424
430
|
) : null)}
|
|
425
431
|
{props.listening ? null : props.start}
|
|
426
432
|
</div>
|
|
427
433
|
<div className={C.end}>
|
|
428
434
|
{props.voiceActive && props.onVoiceMute ? (
|
|
429
|
-
<
|
|
430
|
-
<NmIcon id={props.voiceMuted ? 'mic-off' : 'mic'} />
|
|
431
|
-
</button>
|
|
435
|
+
<ComposerButton glyph={props.voiceMuted ? 'mic-off' : 'mic'} label={props.voiceMuted ? COPY.unmute : COPY.mute} pressed={props.voiceMuted === true} testId="voice-mic-toggle" shape={props.buttonShape} disabled={props.disabled} onClick={props.onVoiceMute} />
|
|
432
436
|
) : props.onMic && !props.listening && !props.voiceActive ? (
|
|
433
|
-
<
|
|
434
|
-
<NmIcon id="mic" />
|
|
435
|
-
</button>
|
|
437
|
+
<ComposerButton glyph="mic" label={COPY.mic} shape={props.buttonShape} disabled={props.disabled} onClick={props.onMic} />
|
|
436
438
|
) : null}
|
|
437
439
|
{props.listening ? null : props.end}
|
|
438
440
|
{action}
|
|
@@ -327,6 +327,10 @@
|
|
|
327
327
|
gap: var(--nm-space-8);
|
|
328
328
|
padding: var(--nm-space-8);
|
|
329
329
|
}
|
|
330
|
+
/* A group in the footer takes the footer's padding, so its rows line up with the rows above. */
|
|
331
|
+
.nm-sidebar__footer > .nm-sidebar__group {
|
|
332
|
+
padding: 0;
|
|
333
|
+
}
|
|
330
334
|
.nm-sidebar__content {
|
|
331
335
|
display: flex;
|
|
332
336
|
flex: 1;
|
|
@@ -41,8 +41,6 @@ export const NM_COMPOSER_CLASSES = {
|
|
|
41
41
|
bar: 'nm-composer__bar',
|
|
42
42
|
start: 'nm-composer__start',
|
|
43
43
|
end: 'nm-composer__end',
|
|
44
|
-
button: 'nm-composer__button',
|
|
45
|
-
primary: 'nm-composer__button--primary',
|
|
46
44
|
stopMark: 'nm-composer__stop-mark',
|
|
47
45
|
} as const;
|
|
48
46
|
/** Set on the root with a shape value; every button in the bar, own or hosted, takes that shape. */
|