@dataverse-kit/agent-kit 0.2.0 → 0.2.1

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.d.cts CHANGED
@@ -94,19 +94,26 @@ interface ComposerProps {
94
94
  maxLength?: number;
95
95
  /** 'enter' sends on Enter; 'mod+enter' needs Ctrl/Cmd. */
96
96
  sendOn?: 'enter' | 'mod+enter';
97
- /** Rendered left of the send button — attachments, formatting, etc. */
97
+ /** Rendered inside the box, left of the send button — attachments, formatting, emoji. */
98
98
  toolbar?: React$1.ReactNode;
99
99
  }
100
100
  /**
101
- * The send box.
101
+ * The send box, shaped like the Teams composer: one rounded container holding a borderless
102
+ * field, the host's affordances, and the send button behind a divider.
102
103
  *
103
104
  * ★ `sendOn` is configurable, and defaults to Enter only because that is what users expect
104
105
  * from Teams. Enter-to-send in a multiline field is hostile to dictation and voice control,
105
- * and Shift+Enter is undiscoverable — hence the `aria-describedby` hint, which makes the
106
- * contract audible instead of folklore.
106
+ * and Shift+Enter is undiscoverable — hence the `aria-describedby` hint. The hint is
107
+ * VISUALLY HIDDEN, not removed: Teams does not print it, but a screen-reader user has no
108
+ * other way to learn the contract.
109
+ *
110
+ * ★ Growth is by HARD NEWLINE only, to `MAX_ROWS`, then the field scrolls. It does not grow on
111
+ * soft wrap — that needs measurement, and a ResizeObserver here would be untestable in jsdom
112
+ * (a stubbed one never fires). Stated rather than implied, because Teams does grow on wrap.
107
113
  *
108
114
  * ★ Deliberately NOT Fluent's `Toolbar` for the affordance row: it reaches
109
115
  * `useArrowNavigationGroup`. Consumers pass plain buttons, or the in-tree ActionToolbar.
116
+ * The kit ships no emoji/GIF/format pickers — those are host concerns; `toolbar` is the slot.
110
117
  */
111
118
  declare function Composer(props: ComposerProps): React$1.ReactElement;
112
119
 
@@ -114,10 +121,16 @@ interface SuggestedPromptsProps {
114
121
  prompts: readonly string[];
115
122
  onPick: (prompt: string) => void;
116
123
  heading?: string;
124
+ /**
125
+ * Row alignment. Defaults to `'start'`, which is right above a composer where the chips read as
126
+ * a continuation of the transcript. `'center'` is for a centred zero state — see
127
+ * `EmptyConversation`, where left-packed chips under a centred heading looked like a defect.
128
+ */
129
+ align?: 'start' | 'center';
117
130
  }
118
131
  /** ★ Plain Buttons in a labelled list, not Fluent's `TagGroup` — `react-tags` reaches
119
132
  * `useArrowNavigationGroup`, and chips gain nothing from it here. */
120
- declare function SuggestedPrompts({ prompts, onPick, heading, }: SuggestedPromptsProps): React$1.ReactElement | null;
133
+ declare function SuggestedPrompts({ prompts, onPick, heading, align, }: SuggestedPromptsProps): React$1.ReactElement | null;
121
134
 
122
135
  interface EmptyConversationProps {
123
136
  title?: string;
package/dist/index.d.ts CHANGED
@@ -94,19 +94,26 @@ interface ComposerProps {
94
94
  maxLength?: number;
95
95
  /** 'enter' sends on Enter; 'mod+enter' needs Ctrl/Cmd. */
96
96
  sendOn?: 'enter' | 'mod+enter';
97
- /** Rendered left of the send button — attachments, formatting, etc. */
97
+ /** Rendered inside the box, left of the send button — attachments, formatting, emoji. */
98
98
  toolbar?: React$1.ReactNode;
99
99
  }
100
100
  /**
101
- * The send box.
101
+ * The send box, shaped like the Teams composer: one rounded container holding a borderless
102
+ * field, the host's affordances, and the send button behind a divider.
102
103
  *
103
104
  * ★ `sendOn` is configurable, and defaults to Enter only because that is what users expect
104
105
  * from Teams. Enter-to-send in a multiline field is hostile to dictation and voice control,
105
- * and Shift+Enter is undiscoverable — hence the `aria-describedby` hint, which makes the
106
- * contract audible instead of folklore.
106
+ * and Shift+Enter is undiscoverable — hence the `aria-describedby` hint. The hint is
107
+ * VISUALLY HIDDEN, not removed: Teams does not print it, but a screen-reader user has no
108
+ * other way to learn the contract.
109
+ *
110
+ * ★ Growth is by HARD NEWLINE only, to `MAX_ROWS`, then the field scrolls. It does not grow on
111
+ * soft wrap — that needs measurement, and a ResizeObserver here would be untestable in jsdom
112
+ * (a stubbed one never fires). Stated rather than implied, because Teams does grow on wrap.
107
113
  *
108
114
  * ★ Deliberately NOT Fluent's `Toolbar` for the affordance row: it reaches
109
115
  * `useArrowNavigationGroup`. Consumers pass plain buttons, or the in-tree ActionToolbar.
116
+ * The kit ships no emoji/GIF/format pickers — those are host concerns; `toolbar` is the slot.
110
117
  */
111
118
  declare function Composer(props: ComposerProps): React$1.ReactElement;
112
119
 
@@ -114,10 +121,16 @@ interface SuggestedPromptsProps {
114
121
  prompts: readonly string[];
115
122
  onPick: (prompt: string) => void;
116
123
  heading?: string;
124
+ /**
125
+ * Row alignment. Defaults to `'start'`, which is right above a composer where the chips read as
126
+ * a continuation of the transcript. `'center'` is for a centred zero state — see
127
+ * `EmptyConversation`, where left-packed chips under a centred heading looked like a defect.
128
+ */
129
+ align?: 'start' | 'center';
117
130
  }
118
131
  /** ★ Plain Buttons in a labelled list, not Fluent's `TagGroup` — `react-tags` reaches
119
132
  * `useArrowNavigationGroup`, and chips gain nothing from it here. */
120
- declare function SuggestedPrompts({ prompts, onPick, heading, }: SuggestedPromptsProps): React$1.ReactElement | null;
133
+ declare function SuggestedPrompts({ prompts, onPick, heading, align, }: SuggestedPromptsProps): React$1.ReactElement | null;
121
134
 
122
135
  interface EmptyConversationProps {
123
136
  title?: string;
package/dist/index.mjs CHANGED
@@ -1190,19 +1190,76 @@ function MessageList(props) {
1190
1190
 
1191
1191
  // src/conversation/Composer.tsx
1192
1192
  import * as React11 from "react";
1193
- import { Button as Button4, Textarea, makeStyles as makeStyles11, tokens as tokens10, Tooltip as Tooltip2 } from "@fluentui/react-components";
1193
+ import { Button as Button4, Textarea, makeStyles as makeStyles11, mergeClasses as mergeClasses5, tokens as tokens10, Tooltip as Tooltip2 } from "@fluentui/react-components";
1194
1194
  import { SendRegular, StopRegular } from "@fluentui/react-icons";
1195
1195
  import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1196
+ var MAX_ROWS = 6;
1196
1197
  var useStyles10 = makeStyles11({
1197
- root: {
1198
- display: "flex",
1199
- gap: tokens10.spacingHorizontalS,
1200
- alignItems: "flex-end",
1198
+ wrapper: {
1201
1199
  padding: tokens10.spacingVerticalS,
1202
1200
  borderTop: `1px solid ${tokens10.colorNeutralStroke2}`
1203
1201
  },
1204
- input: { flex: 1 },
1205
- hint: { fontSize: tokens10.fontSizeBase100, color: tokens10.colorNeutralForeground3 }
1202
+ // The Teams send box: ONE rounded container holding the field and the affordances.
1203
+ box: {
1204
+ display: "flex",
1205
+ alignItems: "flex-end",
1206
+ gap: tokens10.spacingHorizontalXXS,
1207
+ paddingLeft: tokens10.spacingHorizontalS,
1208
+ paddingRight: tokens10.spacingHorizontalXXS,
1209
+ paddingTop: tokens10.spacingVerticalXXS,
1210
+ paddingBottom: tokens10.spacingVerticalXXS,
1211
+ borderRadius: tokens10.borderRadiusXLarge,
1212
+ border: `1px solid ${tokens10.colorNeutralStroke1}`,
1213
+ backgroundColor: tokens10.colorNeutralBackground1,
1214
+ // ★ The ring moves to the CONTAINER because the field's own indicator is suppressed below.
1215
+ // It is an `outline`, not a box-shadow, so forced-colors still paints it (a11y rule 5).
1216
+ ":focus-within": {
1217
+ border: `1px solid ${tokens10.colorCompoundBrandStroke}`,
1218
+ outline: `2px solid ${tokens10.colorCompoundBrandStroke}`,
1219
+ outlineOffset: "-1px"
1220
+ }
1221
+ },
1222
+ boxDisabled: {
1223
+ backgroundColor: tokens10.colorNeutralBackgroundDisabled,
1224
+ border: `1px solid ${tokens10.colorNeutralStrokeDisabled}`
1225
+ },
1226
+ // Fluent's Textarea has no transparent appearance, so its chrome is removed here and the
1227
+ // container above supplies it. `::after` is Fluent's focus underline.
1228
+ field: {
1229
+ flexGrow: 1,
1230
+ minWidth: 0,
1231
+ backgroundColor: "transparent",
1232
+ border: "none",
1233
+ borderRadius: 0,
1234
+ "::after": { display: "none" }
1235
+ },
1236
+ fieldInner: {
1237
+ backgroundColor: "transparent",
1238
+ // ★ Fluent's Textarea ships `min-height: 52px` (measured). The affordance buttons are 32px,
1239
+ // so the default left the placeholder on one line and the icons on another — the single
1240
+ // visible difference from the Teams reference. Matching the button height puts them on
1241
+ // one baseline; growth past one row is then driven by `rows`, up to maxHeight.
1242
+ minHeight: "32px",
1243
+ maxHeight: "160px",
1244
+ paddingLeft: 0,
1245
+ paddingRight: 0
1246
+ },
1247
+ actions: {
1248
+ display: "flex",
1249
+ alignItems: "center",
1250
+ gap: tokens10.spacingHorizontalXXS
1251
+ // ★ No extra bottom padding: `alignItems: flex-end` on the box already pins this row to the
1252
+ // field's last line, and padding on top of it pushed the send glyph visibly below the
1253
+ // baseline on a single-row box (measured against the Teams reference).
1254
+ },
1255
+ divider: {
1256
+ width: "1px",
1257
+ alignSelf: "center",
1258
+ height: "20px",
1259
+ marginLeft: tokens10.spacingHorizontalXXS,
1260
+ marginRight: tokens10.spacingHorizontalXXS,
1261
+ backgroundColor: tokens10.colorNeutralStroke2
1262
+ }
1206
1263
  });
1207
1264
  function Composer(props) {
1208
1265
  const {
@@ -1218,6 +1275,7 @@ function Composer(props) {
1218
1275
  toolbar
1219
1276
  } = props;
1220
1277
  const styles = useStyles10();
1278
+ const vh = useVisuallyHiddenStyles();
1221
1279
  const hintId = React11.useId();
1222
1280
  const submit = () => {
1223
1281
  const text = value.trim();
@@ -1232,56 +1290,77 @@ function Composer(props) {
1232
1290
  submit();
1233
1291
  };
1234
1292
  const hint = sendOn === "enter" ? "Press Enter to send, Shift plus Enter for a new line." : "Press Control or Command plus Enter to send.";
1235
- return /* @__PURE__ */ jsxs9("div", { className: styles.root, children: [
1236
- /* @__PURE__ */ jsxs9("div", { className: styles.input, children: [
1293
+ const rows = Math.min(MAX_ROWS, Math.max(1, value.split("\n").length));
1294
+ return /* @__PURE__ */ jsxs9("div", { className: styles.wrapper, children: [
1295
+ /* @__PURE__ */ jsxs9("div", { className: mergeClasses5(styles.box, disabled && styles.boxDisabled), children: [
1237
1296
  /* @__PURE__ */ jsx12(
1238
1297
  Textarea,
1239
1298
  {
1299
+ className: styles.field,
1240
1300
  value,
1241
1301
  onChange: (_, data) => onChange(data.value),
1242
1302
  onKeyDown,
1243
1303
  placeholder,
1244
1304
  disabled,
1245
1305
  maxLength,
1246
- resize: "vertical",
1306
+ resize: "none",
1307
+ appearance: "outline",
1247
1308
  "aria-label": placeholder,
1248
1309
  "aria-describedby": hintId,
1249
- textarea: { "aria-multiline": true }
1310
+ textarea: { "aria-multiline": true, rows, className: styles.fieldInner }
1250
1311
  }
1251
1312
  ),
1252
- /* @__PURE__ */ jsx12("span", { id: hintId, className: styles.hint, children: hint })
1313
+ /* @__PURE__ */ jsxs9("div", { className: styles.actions, children: [
1314
+ toolbar,
1315
+ toolbar ? /* @__PURE__ */ jsx12("span", { className: styles.divider, "aria-hidden": "true" }) : null,
1316
+ busy && onStop ? /* @__PURE__ */ jsx12(Tooltip2, { content: "Stop generating", relationship: "label", withArrow: true, children: /* @__PURE__ */ jsx12(Button4, { appearance: "subtle", icon: /* @__PURE__ */ jsx12(StopRegular, {}), onClick: onStop }) }) : /* @__PURE__ */ jsx12(Tooltip2, { content: "Send", relationship: "label", withArrow: true, children: /* @__PURE__ */ jsx12(
1317
+ Button4,
1318
+ {
1319
+ appearance: "subtle",
1320
+ icon: /* @__PURE__ */ jsx12(SendRegular, {}),
1321
+ onClick: submit,
1322
+ disabled: !value.trim() || busy || disabled
1323
+ }
1324
+ ) })
1325
+ ] })
1253
1326
  ] }),
1254
- toolbar,
1255
- busy && onStop ? /* @__PURE__ */ jsx12(Tooltip2, { content: "Stop generating", relationship: "label", withArrow: true, children: /* @__PURE__ */ jsx12(Button4, { appearance: "secondary", icon: /* @__PURE__ */ jsx12(StopRegular, {}), onClick: onStop }) }) : /* @__PURE__ */ jsx12(Tooltip2, { content: "Send", relationship: "label", withArrow: true, children: /* @__PURE__ */ jsx12(
1256
- Button4,
1257
- {
1258
- appearance: "primary",
1259
- icon: /* @__PURE__ */ jsx12(SendRegular, {}),
1260
- onClick: submit,
1261
- disabled: !value.trim() || busy || disabled
1262
- }
1263
- ) })
1327
+ /* @__PURE__ */ jsx12("span", { id: hintId, className: vh.root, children: hint })
1264
1328
  ] });
1265
1329
  }
1266
1330
 
1267
1331
  // src/conversation/SuggestedPrompts.tsx
1268
- import { Button as Button5, makeStyles as makeStyles12, tokens as tokens11 } from "@fluentui/react-components";
1332
+ import { Button as Button5, makeStyles as makeStyles12, mergeClasses as mergeClasses6, tokens as tokens11 } from "@fluentui/react-components";
1269
1333
  import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1270
1334
  var useStyles11 = makeStyles12({
1271
1335
  root: { display: "flex", flexWrap: "wrap", gap: tokens11.spacingHorizontalXS, padding: tokens11.spacingVerticalXS },
1336
+ // ★ The heading is `width: 100%`, so it takes the PARENT's text-align while the chips take the
1337
+ // flex row's justification. That split is why the label could look centred while the chips
1338
+ // stayed left-packed — the two are aligned by different mechanisms and must both be set.
1339
+ center: { justifyContent: "center" },
1340
+ headingCenter: { textAlign: "center" },
1272
1341
  heading: { width: "100%", fontSize: tokens11.fontSizeBase200, color: tokens11.colorNeutralForeground3 }
1273
1342
  });
1274
1343
  function SuggestedPrompts({
1275
1344
  prompts,
1276
1345
  onPick,
1277
- heading = "Suggested follow-ups"
1346
+ heading = "Suggested follow-ups",
1347
+ align = "start"
1278
1348
  }) {
1279
1349
  const styles = useStyles11();
1280
1350
  if (prompts.length === 0) return null;
1281
- return /* @__PURE__ */ jsxs10("div", { className: styles.root, role: "group", "aria-label": heading, children: [
1282
- /* @__PURE__ */ jsx13("span", { className: styles.heading, children: heading }),
1283
- prompts.map((prompt) => /* @__PURE__ */ jsx13(Button5, { size: "small", appearance: "outline", onClick: () => onPick(prompt), children: prompt }, prompt))
1284
- ] });
1351
+ const centered = align === "center";
1352
+ return /* @__PURE__ */ jsxs10(
1353
+ "div",
1354
+ {
1355
+ className: mergeClasses6(styles.root, centered && styles.center),
1356
+ role: "group",
1357
+ "aria-label": heading,
1358
+ children: [
1359
+ /* @__PURE__ */ jsx13("span", { className: mergeClasses6(styles.heading, centered && styles.headingCenter), children: heading }),
1360
+ prompts.map((prompt) => /* @__PURE__ */ jsx13(Button5, { size: "small", appearance: "outline", onClick: () => onPick(prompt), children: prompt }, prompt))
1361
+ ]
1362
+ }
1363
+ );
1285
1364
  }
1286
1365
 
1287
1366
  // src/conversation/EmptyConversation.tsx
@@ -1310,7 +1389,7 @@ function EmptyConversation({
1310
1389
  return /* @__PURE__ */ jsxs11("div", { className: styles.root, children: [
1311
1390
  /* @__PURE__ */ jsx14(Text2, { as: "h2", size: 500, weight: "semibold", children: title }),
1312
1391
  /* @__PURE__ */ jsx14(Text2, { children: description }),
1313
- onPick && prompts.length > 0 ? /* @__PURE__ */ jsx14(SuggestedPrompts, { prompts, onPick, heading: "Try one of these" }) : null
1392
+ onPick && prompts.length > 0 ? /* @__PURE__ */ jsx14(SuggestedPrompts, { prompts, onPick, heading: "Try one of these", align: "center" }) : null
1314
1393
  ] });
1315
1394
  }
1316
1395
 
@@ -1760,7 +1839,7 @@ import {
1760
1839
  MenuItem,
1761
1840
  Tooltip as Tooltip4,
1762
1841
  makeStyles as makeStyles19,
1763
- mergeClasses as mergeClasses5,
1842
+ mergeClasses as mergeClasses7,
1764
1843
  tokens as tokens18
1765
1844
  } from "@fluentui/react-components";
1766
1845
  import { MoreHorizontalRegular } from "@fluentui/react-icons";
@@ -1807,7 +1886,7 @@ function AppRail(props) {
1807
1886
  {
1808
1887
  appearance: "subtle",
1809
1888
  icon: item.icon,
1810
- className: mergeClasses5(styles.button, item.id === selectedId && styles.selected),
1889
+ className: mergeClasses7(styles.button, item.id === selectedId && styles.selected),
1811
1890
  "aria-pressed": item.id === selectedId,
1812
1891
  "aria-label": item.badgeCount ? `${item.label}, ${item.badgeCount} unread` : item.label,
1813
1892
  onClick: () => onSelect(item.id),
@@ -1836,7 +1915,7 @@ import * as React15 from "react";
1836
1915
  import { makeStyles as makeStyles21, tokens as tokens20, Text as Text6 } from "@fluentui/react-components";
1837
1916
 
1838
1917
  // src/shell/ConversationListItem.tsx
1839
- import { makeStyles as makeStyles20, mergeClasses as mergeClasses6, tokens as tokens19 } from "@fluentui/react-components";
1918
+ import { makeStyles as makeStyles20, mergeClasses as mergeClasses8, tokens as tokens19 } from "@fluentui/react-components";
1840
1919
 
1841
1920
  // src/shell/AgentAvatar.tsx
1842
1921
  import { Avatar as Avatar2 } from "@fluentui/react-components";
@@ -1935,13 +2014,13 @@ function ConversationListItem(props) {
1935
2014
  role: "option",
1936
2015
  "aria-selected": selected,
1937
2016
  "aria-label": conversationAccessibleName(summary, locale),
1938
- className: mergeClasses6(styles.row, selected && styles.selected, active && styles.active),
2017
+ className: mergeClasses8(styles.row, selected && styles.selected, active && styles.active),
1939
2018
  onClick: () => onSelect(summary.id),
1940
2019
  "data-conversation-option": true,
1941
2020
  children: [
1942
2021
  /* @__PURE__ */ jsx28(AgentAvatar, { participant: summary.participant, presence: summary.presence, size: 32 }),
1943
2022
  /* @__PURE__ */ jsxs21("div", { className: styles.body, "aria-hidden": "true", children: [
1944
- /* @__PURE__ */ jsx28("span", { className: mergeClasses6(styles.title, summary.muted && styles.muted), children: summary.title }),
2023
+ /* @__PURE__ */ jsx28("span", { className: mergeClasses8(styles.title, summary.muted && styles.muted), children: summary.title }),
1945
2024
  summary.preview ? /* @__PURE__ */ jsx28("span", { className: styles.preview, children: summary.preview }) : null
1946
2025
  ] }),
1947
2026
  /* @__PURE__ */ jsxs21("div", { className: styles.meta, "aria-hidden": "true", children: [