rift-ds 1.9.0 → 1.10.0

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.
Files changed (79) hide show
  1. package/README.md +3 -3
  2. package/bin/templates/chat/app/api/chat/route.ts +3 -0
  3. package/chat/AssistantTurn.js +5 -2
  4. package/chat/ChatWidget.css +5 -4
  5. package/chat/ChatWidget.d.ts +10 -1
  6. package/chat/ChatWidget.js +17 -4
  7. package/chat/DemoWelcome.js +17 -5
  8. package/chat/FollowupSuggestions.d.ts +7 -4
  9. package/chat/FollowupSuggestions.js +5 -3
  10. package/chat/TurnQuestion.d.ts +19 -0
  11. package/chat/TurnQuestion.js +38 -0
  12. package/chat/demo-script.js +256 -50
  13. package/chat/followups.d.ts +2 -2
  14. package/chat/followups.js +1 -0
  15. package/chat/index.d.ts +2 -1
  16. package/chat/index.js +11 -0
  17. package/chat/queries.d.ts +2 -2
  18. package/chat/queries.js +3 -2
  19. package/chat/questions.d.ts +97 -0
  20. package/chat/questions.js +140 -0
  21. package/chat/registry.json.d.ts +3 -2
  22. package/chat/registry.json.js +1 -1
  23. package/chat/server.d.ts +19 -0
  24. package/chat/server.js +45 -1
  25. package/chat/sim.d.ts +12 -1
  26. package/chat/sim.js +19 -3
  27. package/chat/types.d.ts +58 -1
  28. package/chat/types.js +1 -0
  29. package/chat/useChat.d.ts +16 -1
  30. package/chat/useChat.js +39 -2
  31. package/components/AlertDialog/AlertDialog.css +1 -1
  32. package/components/AnchorNav/AnchorNav.css +1 -1
  33. package/components/AppSidebar/AppSidebar.css +5 -5
  34. package/components/AppSidebar/AppSidebar.js +3 -2
  35. package/components/Attachment/Attachment.css +3 -1
  36. package/components/BottomNav/BottomNav.css +1 -1
  37. package/components/Chart/Chart.css +1 -0
  38. package/components/ChatThread/ChatThread.css +21 -0
  39. package/components/ChatThread/ChatThread.d.ts +12 -1
  40. package/components/ChatThread/ChatThread.js +40 -1
  41. package/components/ColorPicker/ColorPicker.css +1 -1
  42. package/components/Combobox/Combobox.css +1 -1
  43. package/components/CommandPalette/CommandPalette.css +1 -1
  44. package/components/Composer/Composer.css +1 -1
  45. package/components/ContextMenu/ContextMenu.css +1 -1
  46. package/components/Dialog/Dialog.css +1 -1
  47. package/components/Drawer/Drawer.css +1 -1
  48. package/components/Dropdown/Dropdown.css +1 -1
  49. package/components/DropdownMenu/DropdownMenu.css +1 -1
  50. package/components/FilterBar/FilterBar.css +1 -1
  51. package/components/HoverCard/HoverCard.css +1 -1
  52. package/components/Lightbox/Lightbox.css +1 -1
  53. package/components/ModelPicker/ModelPicker.css +1 -1
  54. package/components/Popover/Popover.css +1 -1
  55. package/components/PromptSuggestions/PromptSuggestions.css +67 -0
  56. package/components/PromptSuggestions/PromptSuggestions.d.ts +8 -0
  57. package/components/PromptSuggestions/PromptSuggestions.js +34 -26
  58. package/components/QuestionCard/QuestionCard.css +358 -0
  59. package/components/QuestionCard/QuestionCard.d.ts +136 -0
  60. package/components/QuestionCard/QuestionCard.js +371 -0
  61. package/components/RichDropdown/RichDropdown.css +1 -1
  62. package/components/SourceChip/SourceChip.css +1 -1
  63. package/components/TimePicker/TimePicker.css +1 -1
  64. package/components/Toast/Toast.css +1 -1
  65. package/components/Tooltip/Tooltip.css +1 -1
  66. package/components/TopAppBar/TopAppBar.css +1 -1
  67. package/components/registry.json +8 -0
  68. package/components/registry.json.d.ts +8 -0
  69. package/components/registry.json.js +1 -1
  70. package/index.d.ts +1 -0
  71. package/index.js +3 -0
  72. package/package.json +5 -1
  73. package/tokens/breakpoints.d.ts +60 -0
  74. package/tokens/breakpoints.js +92 -0
  75. package/tokens/registry.d.ts +1 -1
  76. package/tokens/registry.json +14 -0
  77. package/tokens/registry.json.d.ts +14 -0
  78. package/tokens/registry.json.js +1 -1
  79. package/tokens/tokens-light.css +22 -0
@@ -4,6 +4,7 @@ import React, { useState, useRef, useEffect, useCallback, useId } from "react";
4
4
  import { useLayer } from "../../behaviors/useLayer.js";
5
5
  import { useFocusScope } from "../../behaviors/useFocusScope.js";
6
6
  import { useScrollLock } from "../../behaviors/useScrollLock.js";
7
+ import { mediaAbove, mediaBelow, BREAKPOINT_TABLET_PX, BREAKPOINT_MOBILE_PX } from "../../tokens/breakpoints.js";
7
8
  import { Avatar } from "../Avatar/Avatar.js";
8
9
  import { PixelAvatar } from "../PixelAvatar/PixelAvatar.js";
9
10
  import { SidebarPanel } from "../SidebarPanel/SidebarPanel.js";
@@ -71,7 +72,7 @@ const AppSidebar = ({
71
72
  onExpandedChange?.(next);
72
73
  }, [isExpanded, onExpandedChange]);
73
74
  useEffect(() => {
74
- const band = window.matchMedia("(min-width: 769px) and (max-width: 959px)");
75
+ const band = window.matchMedia(`${mediaAbove(BREAKPOINT_MOBILE_PX)} and ${mediaBelow(BREAKPOINT_TABLET_PX)}`);
75
76
  const onChange = () => {
76
77
  if (band.matches) {
77
78
  setInternalExpanded(false);
@@ -91,7 +92,7 @@ const AppSidebar = ({
91
92
  useScrollLock(mobileOpen);
92
93
  useEffect(() => {
93
94
  if (!mobileOpen) return;
94
- const query = window.matchMedia("(min-width: 769px)");
95
+ const query = window.matchMedia(mediaAbove(BREAKPOINT_MOBILE_PX));
95
96
  const onChange = () => {
96
97
  if (query.matches) setMobileOpen(false);
97
98
  };
@@ -703,7 +703,7 @@ button.ds-attachment-group__single:focus-visible {
703
703
  .ds-attachment-viewer {
704
704
  position: fixed;
705
705
  inset: 0;
706
- z-index: 100;
706
+ z-index: var(--layer-modal);
707
707
  display: flex;
708
708
  align-items: center;
709
709
  justify-content: center;
@@ -934,6 +934,7 @@ button.ds-attachment-group__single:focus-visible {
934
934
  Below 30rem there is no room for a floating dialog and its margin, so
935
935
  the panel takes the whole viewport. */
936
936
 
937
+ /* ds-allow(breakpoint): a rem query, so a reader with a larger default font gets the full-screen sheet sooner; it is the sheet step (480px) at the default size, and rewriting it in px would change when a large-text reader sees it. */
937
938
  @media (max-width: 30rem) {
938
939
  .ds-attachment-viewer {
939
940
  padding: 0;
@@ -966,6 +967,7 @@ button.ds-attachment-group__single:focus-visible {
966
967
  /* Flush with the surface it covers, corners and all, so the edge reads
967
968
  as the surface's own outline lighting up */
968
969
  inset: 0;
970
+ /* ds-allow(stacking): local order, above the surface the drop zone covers; it never leaves that surface's stacking context. */
969
971
  z-index: 10;
970
972
  display: flex;
971
973
  align-items: center;
@@ -26,7 +26,7 @@
26
26
  left: 0;
27
27
  right: 0;
28
28
  bottom: 0;
29
- z-index: 80;
29
+ z-index: var(--layer-nav);
30
30
  }
31
31
 
32
32
  .ds-bottom-nav__bar {
@@ -201,6 +201,7 @@
201
201
  RESPONSIVE — stack header on small screens
202
202
  ============================================ */
203
203
 
204
+ /* ds-allow(breakpoint): the phone step on its other edge (below 600px, not at or below it); aligning it would change this rule at exactly 600px. */
204
205
  @media (max-width: 599px) {
205
206
  .ds-chart {
206
207
  padding: var(--padding-400);
@@ -169,3 +169,24 @@
169
169
  .ds-chat-thread__jump-icon {
170
170
  --icon-size: var(--icon-size-600);
171
171
  }
172
+
173
+ /* ============================================
174
+ BOTTOM-UP — the classic message thread
175
+ Turns stack from the bottom edge: a short
176
+ conversation sits just above the composer
177
+ rather than at the top of an empty column.
178
+ The content fills the viewport at least and
179
+ pushes its turns to the end of it.
180
+ ============================================ */
181
+
182
+ .ds-chat-thread--bottom-up {
183
+ display: flex;
184
+ flex-direction: column;
185
+ }
186
+
187
+ .ds-chat-thread--bottom-up .ds-chat-thread__content {
188
+ box-sizing: border-box;
189
+ flex: 1 0 auto;
190
+ justify-content: flex-end;
191
+ width: 100%;
192
+ }
@@ -10,6 +10,16 @@ type ChatThreadOwnProps = {
10
10
  * edit-last-turn) neither re-anchors nor resizes the spacer.
11
11
  */
12
12
  anchor?: boolean;
13
+ /**
14
+ * Which way the conversation reads. `top-down` (the default) is the send
15
+ * choreography above: each new turn floats to the top of the viewport and
16
+ * the answer streams in below it. `bottom-up` is the classic message
17
+ * thread: turns stack from the bottom edge, the newest always just above
18
+ * the composer, and the view follows a streaming answer down for as long
19
+ * as the reader stays at the bottom. Scrolling up to reread lets go;
20
+ * returning to the bottom, or the next send, takes hold again.
21
+ */
22
+ flow?: 'top-down' | 'bottom-up';
13
23
  /** Accessible name for the scrollable conversation region. */
14
24
  ariaLabel?: string;
15
25
  /** Accessible name for the scroll-to-bottom control. */
@@ -26,7 +36,8 @@ export interface ChatThreadProps extends ChatThreadOwnProps, Omit<React.Componen
26
36
  * pure-CSS fade at either end so content scrolling out passes through it,
27
37
  * and the send choreography — a newly appended turn floats to the top of
28
38
  * the viewport while the prior conversation is pushed upward, leaving the
29
- * response room to stream in below.
39
+ * response room to stream in below. `flow="bottom-up"` swaps that for the
40
+ * classic thread, stacked from the bottom and following the newest turn.
30
41
  *
31
42
  * A trailing spacer grows just enough that even a short newest exchange can
32
43
  * reach the top, so the anchor position never depends on how much has been
@@ -8,6 +8,7 @@ const JUMP_THRESHOLD_PX = 24;
8
8
  const ChatThread = React.forwardRef(
9
9
  ({
10
10
  anchor = true,
11
+ flow = "top-down",
11
12
  ariaLabel = "Conversation",
12
13
  jumpLabel = "Scroll to the latest message",
13
14
  className = "",
@@ -22,6 +23,18 @@ const ChatThread = React.forwardRef(
22
23
  const prevCount = useRef(0);
23
24
  const mounted = useRef(false);
24
25
  const anchorTop = useRef(null);
26
+ const bottomUp = flow === "bottom-up";
27
+ const flowRef = useRef(flow);
28
+ flowRef.current = flow;
29
+ const stuck = useRef(true);
30
+ const lastScrollTop = useRef(0);
31
+ const scrollToEnd = (behavior) => {
32
+ const container = scrollRef.current;
33
+ if (!container) return;
34
+ const top = container.scrollHeight - container.clientHeight;
35
+ if (behavior) container.scrollTo({ top, behavior });
36
+ else container.scrollTo({ top });
37
+ };
25
38
  const sizeSpacer = () => {
26
39
  const container = scrollRef.current;
27
40
  const content = contentRef.current;
@@ -45,6 +58,7 @@ const ChatThread = React.forwardRef(
45
58
  const wasClamped = top != null && container.scrollTop < top && container.scrollTop >= container.scrollHeight - container.clientHeight - 1;
46
59
  sizeSpacer();
47
60
  if (top != null && wasClamped) container.scrollTo({ top, behavior: "instant" });
61
+ if (flowRef.current === "bottom-up" && stuck.current) scrollToEnd("instant");
48
62
  updateJump();
49
63
  });
50
64
  observer.observe(container);
@@ -78,6 +92,19 @@ const ChatThread = React.forwardRef(
78
92
  mounted.current = true;
79
93
  if (childCount === previous) return;
80
94
  prevCount.current = childCount;
95
+ if (bottomUp) {
96
+ anchorTop.current = null;
97
+ spacer.style.height = "0px";
98
+ if (childCount < previous) {
99
+ container.scrollTo({ top: 0 });
100
+ return;
101
+ }
102
+ if (childCount === 0 || !anchor) return;
103
+ stuck.current = true;
104
+ scrollToEnd(isFirstRun ? "instant" : void 0);
105
+ updateJump();
106
+ return;
107
+ }
81
108
  if (childCount < previous) {
82
109
  anchorTop.current = null;
83
110
  spacer.style.height = "0px";
@@ -124,6 +151,13 @@ const ChatThread = React.forwardRef(
124
151
  useEffect(updateJump);
125
152
  const handleScroll = (e) => {
126
153
  onScroll?.(e);
154
+ if (bottomUp) {
155
+ const container = e.currentTarget;
156
+ const atEnd = container.scrollHeight - container.scrollTop - container.clientHeight <= JUMP_THRESHOLD_PX;
157
+ if (atEnd) stuck.current = true;
158
+ else if (container.scrollTop < lastScrollTop.current) stuck.current = false;
159
+ lastScrollTop.current = container.scrollTop;
160
+ }
127
161
  updateJump();
128
162
  setScrolling(true);
129
163
  window.clearTimeout(settleTimer.current);
@@ -139,7 +173,12 @@ const ChatThread = React.forwardRef(
139
173
  container.scrollTo({ top: content.offsetHeight - container.clientHeight });
140
174
  };
141
175
  useEffect(() => () => window.clearTimeout(settleTimer.current), []);
142
- const classes = [baseClass, scrolling ? `${baseClass}--scrolling` : "", className].filter(Boolean).join(" ");
176
+ const classes = [
177
+ baseClass,
178
+ bottomUp ? `${baseClass}--bottom-up` : "",
179
+ scrolling ? `${baseClass}--scrolling` : "",
180
+ className
181
+ ].filter(Boolean).join(" ");
143
182
  return /* @__PURE__ */ jsxs(
144
183
  "div",
145
184
  {
@@ -98,7 +98,7 @@
98
98
  border: var(--border-025) solid var(--color-input-border-primary);
99
99
  border-radius: var(--radius-300);
100
100
  box-shadow: var(--shadow-floating);
101
- z-index: 10;
101
+ z-index: var(--layer-dropdown);
102
102
  }
103
103
 
104
104
  /* Saturation / brightness area */
@@ -186,7 +186,7 @@
186
186
  list-style: none;
187
187
  max-height: 280px;
188
188
  overflow-y: auto;
189
- z-index: 10;
189
+ z-index: var(--layer-dropdown);
190
190
  box-shadow: var(--shadow-floating);
191
191
  animation: ds-combobox-appear var(--motion-duration-fast) var(--motion-ease-entrance);
192
192
  }
@@ -6,7 +6,7 @@
6
6
  .ds-command-palette {
7
7
  position: fixed;
8
8
  inset: 0;
9
- z-index: 110; /* above Dialog and Drawer */
9
+ z-index: var(--layer-command); /* above Dialog and Drawer */
10
10
  display: flex;
11
11
  align-items: flex-start;
12
12
  justify-content: center;
@@ -570,7 +570,7 @@
570
570
  .ds-composer__mention-menu {
571
571
  position: absolute;
572
572
  left: 0;
573
- z-index: 10;
573
+ z-index: var(--layer-dropdown);
574
574
  /* A list, not a text field */
575
575
  cursor: default;
576
576
  }
@@ -18,7 +18,7 @@
18
18
 
19
19
  .ds-context-menu__panel {
20
20
  position: fixed;
21
- z-index: 50;
21
+ z-index: var(--layer-floating);
22
22
  min-width: 200px;
23
23
 
24
24
  /* Appearance — same recipe as DropdownMenu */
@@ -6,7 +6,7 @@
6
6
  .ds-dialog {
7
7
  position: fixed;
8
8
  inset: 0;
9
- z-index: 100;
9
+ z-index: var(--layer-modal);
10
10
  display: flex;
11
11
  align-items: center;
12
12
  justify-content: center;
@@ -6,7 +6,7 @@
6
6
  .ds-drawer {
7
7
  position: fixed;
8
8
  inset: 0;
9
- z-index: 100;
9
+ z-index: var(--layer-modal);
10
10
  display: flex;
11
11
  visibility: hidden; /* keeps panel controls out of the tab order when closed */
12
12
  pointer-events: none;
@@ -109,7 +109,7 @@
109
109
  list-style: none;
110
110
  max-height: 240px;
111
111
  overflow-y: auto;
112
- z-index: 10;
112
+ z-index: var(--layer-dropdown);
113
113
  box-shadow: var(--shadow-floating);
114
114
 
115
115
  /* Animation */
@@ -35,7 +35,7 @@
35
35
  position: absolute;
36
36
  top: 100%;
37
37
  left: 0;
38
- z-index: 50;
38
+ z-index: var(--layer-floating);
39
39
  min-width: 200px;
40
40
  margin-top: var(--gap-050); /* 2px offset from trigger */
41
41
 
@@ -126,7 +126,7 @@
126
126
  position: absolute;
127
127
  top: calc(100% + var(--gap-050));
128
128
  left: 0;
129
- z-index: 10;
129
+ z-index: var(--layer-dropdown);
130
130
  min-width: 100%;
131
131
  max-height: 280px;
132
132
  overflow-y: auto;
@@ -18,7 +18,7 @@
18
18
  .ds-hover-card__panel {
19
19
  /* Rendered as a <span> so the card survives inside a <p>. */
20
20
  position: absolute;
21
- z-index: 50;
21
+ z-index: var(--layer-floating);
22
22
  min-width: 240px;
23
23
  max-width: 320px;
24
24
  padding: var(--padding-400);
@@ -6,7 +6,7 @@
6
6
  .ds-lightbox {
7
7
  position: fixed;
8
8
  inset: 0;
9
- z-index: 100;
9
+ z-index: var(--layer-modal);
10
10
  opacity: 0;
11
11
  visibility: hidden; /* keeps panel controls out of the tab order when closed */
12
12
  pointer-events: none;
@@ -75,7 +75,7 @@
75
75
  .ds-model-picker__panel {
76
76
  position: absolute;
77
77
  left: 0;
78
- z-index: 10;
78
+ z-index: var(--layer-dropdown);
79
79
  min-width: 260px;
80
80
  display: flex;
81
81
  flex-direction: column;
@@ -23,7 +23,7 @@
23
23
 
24
24
  .ds-popover__panel {
25
25
  position: absolute;
26
- z-index: 10;
26
+ z-index: var(--layer-dropdown);
27
27
  min-width: 200px;
28
28
  padding: var(--padding-400); /* 16px */
29
29
  border: var(--border-025) solid var(--color-bg-container-border);
@@ -480,3 +480,70 @@
480
480
  transform: translateX(100%);
481
481
  }
482
482
  }
483
+
484
+ /* ============================================
485
+ KEPT — the record of a choice
486
+ A row that stays on screen after use keeps
487
+ the choice in view with QuestionCard's own
488
+ treatment: the taken suggestion outlined
489
+ solid in the strongest ink, doubled by an
490
+ inset ring so it does not grow, and the rest
491
+ faded behind a dashed line. No check: a chip
492
+ is a line of text, and it keeps its shape.
493
+ Nothing in the row answers the pointer.
494
+ ============================================ */
495
+
496
+ .ds-prompt-suggestions--kept .ds-prompt-suggestions__item:not(.ds-prompt-suggestions__item--kept) {
497
+ opacity: 0.4;
498
+ }
499
+
500
+ .ds-prompt-suggestions--kept .ds-chip,
501
+ .ds-prompt-suggestions--kept .ds-prompt-suggestions__tile,
502
+ .ds-prompt-suggestions--kept .ds-prompt-suggestions__card {
503
+ border-style: dashed;
504
+ border-color: var(--color-text-tertiary);
505
+ /* A record carries no fill, like QuestionCard's rows once answered: the
506
+ outline alone says which way the conversation went. */
507
+ background-color: transparent;
508
+ }
509
+
510
+ .ds-prompt-suggestions__item--kept .ds-chip,
511
+ .ds-prompt-suggestions__item--kept .ds-prompt-suggestions__tile,
512
+ .ds-prompt-suggestions__item--kept .ds-prompt-suggestions__card,
513
+ .ds-prompt-suggestions--kept .ds-prompt-suggestions__item--kept .ds-prompt-suggestions__tile:hover {
514
+ border-style: solid;
515
+ border-color: var(--color-text-primary);
516
+ box-shadow: inset 0 0 0 var(--border-025) var(--color-text-primary);
517
+ color: var(--color-text-primary);
518
+ }
519
+
520
+ .ds-prompt-suggestions--kept .ds-prompt-suggestions__tile,
521
+ .ds-prompt-suggestions--kept .ds-prompt-suggestions__card {
522
+ cursor: default;
523
+ }
524
+
525
+ .ds-prompt-suggestions--kept .ds-prompt-suggestions__tile:hover {
526
+ background-color: var(--color-bg-container-primary-semi);
527
+ }
528
+
529
+ .ds-prompt-suggestions--kept .ds-prompt-suggestions__card:hover {
530
+ transform: none;
531
+ }
532
+
533
+ .ds-prompt-suggestions--kept
534
+ .ds-prompt-suggestions__item:not(.ds-prompt-suggestions__item--kept)
535
+ .ds-prompt-suggestions__card:hover {
536
+ box-shadow: none;
537
+ }
538
+
539
+ .ds-prompt-suggestions__sr-only {
540
+ position: absolute;
541
+ width: 1px;
542
+ height: 1px;
543
+ padding: 0;
544
+ margin: -1px;
545
+ overflow: hidden;
546
+ clip: rect(0, 0, 0, 0);
547
+ white-space: nowrap;
548
+ border: 0;
549
+ }
@@ -57,6 +57,14 @@ type PromptSuggestionsOwnProps = {
57
57
  * the quieter row for placements alongside a live conversation.
58
58
  */
59
59
  size?: 'default' | 'compact';
60
+ /**
61
+ * The `id` of the suggestion already taken. Set, the row becomes the
62
+ * record of that choice: the taken suggestion outlined solid in the
63
+ * strongest ink, the rest faded behind a dashed line, and none of them
64
+ * tappable any more. The same treatment QuestionCard gives a pick. For a row that stays
65
+ * on screen after use, such as the follow-ups under an earlier answer.
66
+ */
67
+ value?: string;
60
68
  /** Accessible name for the list. */
61
69
  ariaLabel?: string;
62
70
  /** Additional CSS classes */
@@ -15,6 +15,7 @@ const PromptSuggestions = React.forwardRef(
15
15
  pendingCount = 3,
16
16
  entrance = true,
17
17
  size = "default",
18
+ value,
18
19
  ariaLabel = "Suggested prompts",
19
20
  className = "",
20
21
  ...rest
@@ -28,8 +29,9 @@ const PromptSuggestions = React.forwardRef(
28
29
  },
29
30
  []
30
31
  );
32
+ const kept = value !== void 0;
31
33
  const choose = (id) => {
32
- if (chosenId || chooseTimer.current) return;
34
+ if (kept || chosenId || chooseTimer.current) return;
33
35
  setChosenId(id);
34
36
  chooseTimer.current = window.setTimeout(() => {
35
37
  chooseTimer.current = null;
@@ -45,6 +47,7 @@ const PromptSuggestions = React.forwardRef(
45
47
  `${baseClass}--${size}`,
46
48
  `${baseClass}--${arrangement}`,
47
49
  chosenId ? `${baseClass}--choosing` : "",
50
+ kept ? `${baseClass}--kept` : "",
48
51
  entrance ? "" : `${baseClass}--still`,
49
52
  className
50
53
  ].filter(Boolean).join(" ");
@@ -64,39 +67,44 @@ const PromptSuggestions = React.forwardRef(
64
67
  block === "card" && /* @__PURE__ */ jsx("span", { className: `${baseClass}__cover` }),
65
68
  /* @__PURE__ */ jsx("span", { className: `${baseClass}__skeleton-bar` })
66
69
  ] })
67
- ) : /* @__PURE__ */ jsx("span", { className: `${baseClass}__skeleton`, children: /* @__PURE__ */ jsx("span", { className: `${baseClass}__skeleton-bar` }) }) }, index)) : suggestions.map((suggestion) => /* @__PURE__ */ jsx(
70
+ ) : /* @__PURE__ */ jsx("span", { className: `${baseClass}__skeleton`, children: /* @__PURE__ */ jsx("span", { className: `${baseClass}__skeleton-bar` }) }) }, index)) : suggestions.map((suggestion) => /* @__PURE__ */ jsxs(
68
71
  "div",
69
72
  {
70
73
  role: "listitem",
71
- className: `${baseClass}__item${chosenId === suggestion.id ? ` ${baseClass}__item--chosen` : ""}`,
74
+ className: `${baseClass}__item${chosenId === suggestion.id ? ` ${baseClass}__item--chosen` : ""}${value === suggestion.id ? ` ${baseClass}__item--kept` : ""}`,
72
75
  onAnimationEnd: (event) => {
73
76
  if (event.animationName === "ds-prompt-suggestions-choose") {
74
77
  setChosenId(null);
75
78
  }
76
79
  },
77
- children: block ? /* @__PURE__ */ jsxs(
78
- "button",
79
- {
80
- type: "button",
81
- className: `${baseClass}__${block}`,
82
- onClick: () => choose(suggestion.id),
83
- children: [
84
- block === "card" ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__cover`, children: suggestion.icon != null && renderIcon(suggestion.icon) }) : suggestion.icon != null && renderIcon(suggestion.icon),
85
- /* @__PURE__ */ jsxs("span", { className: `${baseClass}__text`, children: [
86
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: suggestion.label }),
87
- suggestion.description && /* @__PURE__ */ jsx("span", { className: `${baseClass}__description`, children: suggestion.description })
88
- ] })
89
- ]
90
- }
91
- ) : /* @__PURE__ */ jsx(
92
- Chip,
93
- {
94
- label: suggestion.label,
95
- icon: suggestion.icon,
96
- size: chipSize,
97
- onClick: () => choose(suggestion.id)
98
- }
99
- )
80
+ children: [
81
+ block ? /* @__PURE__ */ jsxs(
82
+ "button",
83
+ {
84
+ type: "button",
85
+ className: `${baseClass}__${block}`,
86
+ disabled: kept,
87
+ onClick: () => choose(suggestion.id),
88
+ children: [
89
+ block === "card" ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__cover`, children: suggestion.icon != null && renderIcon(suggestion.icon) }) : suggestion.icon != null && renderIcon(suggestion.icon),
90
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__text`, children: [
91
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: suggestion.label }),
92
+ suggestion.description && /* @__PURE__ */ jsx("span", { className: `${baseClass}__description`, children: suggestion.description })
93
+ ] }),
94
+ value === suggestion.id && /* @__PURE__ */ jsx("span", { className: `${baseClass}__sr-only`, children: " (chosen)" })
95
+ ]
96
+ }
97
+ ) : /* @__PURE__ */ jsx(
98
+ Chip,
99
+ {
100
+ label: suggestion.label,
101
+ icon: suggestion.icon,
102
+ size: chipSize,
103
+ onClick: kept ? void 0 : () => choose(suggestion.id)
104
+ }
105
+ ),
106
+ !block && value === suggestion.id && /* @__PURE__ */ jsx("span", { className: `${baseClass}__sr-only`, children: " (chosen)" })
107
+ ]
100
108
  },
101
109
  suggestion.id
102
110
  ))