@redocly/theme 0.70.0-next.8 → 0.70.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 (101) hide show
  1. package/lib/components/LoadProgressBar/LoadProgressBar.d.ts +8 -0
  2. package/lib/components/LoadProgressBar/LoadProgressBar.js +122 -0
  3. package/lib/core/constants/index.d.ts +1 -0
  4. package/lib/core/constants/index.js +1 -0
  5. package/lib/core/constants/prompt.d.ts +9 -0
  6. package/lib/core/constants/prompt.js +33 -0
  7. package/lib/core/hooks/index.d.ts +3 -0
  8. package/lib/core/hooks/index.js +3 -0
  9. package/lib/core/hooks/prompt/use-prompt-actions.d.ts +7 -0
  10. package/lib/core/hooks/prompt/use-prompt-actions.js +19 -0
  11. package/lib/core/hooks/prompt/use-prompt-text-limit.d.ts +19 -0
  12. package/lib/core/hooks/prompt/use-prompt-text-limit.js +35 -0
  13. package/lib/core/hooks/use-page-actions.js +4 -8
  14. package/lib/core/hooks/use-pointer-border-highlight.d.ts +7 -0
  15. package/lib/core/hooks/use-pointer-border-highlight.js +29 -0
  16. package/lib/core/hooks/use-telemetry-fallback.d.ts +2 -0
  17. package/lib/core/hooks/use-telemetry-fallback.js +2 -0
  18. package/lib/core/styles/dark.js +14 -12
  19. package/lib/core/styles/global.js +16 -54
  20. package/lib/core/types/index.d.ts +1 -0
  21. package/lib/core/types/l10n.d.ts +1 -1
  22. package/lib/core/types/prompt.d.ts +9 -0
  23. package/lib/core/types/prompt.js +3 -0
  24. package/lib/core/utils/get-ai-prompt-link.d.ts +15 -0
  25. package/lib/core/utils/get-ai-prompt-link.js +29 -0
  26. package/lib/core/utils/index.d.ts +1 -0
  27. package/lib/core/utils/index.js +1 -0
  28. package/lib/core/utils/urls.js +1 -1
  29. package/lib/core/utils/with-load-progress.d.ts +3 -0
  30. package/lib/core/utils/with-load-progress.js +24 -19
  31. package/lib/ext/configure.d.ts +1 -1
  32. package/lib/ext/configure.js +0 -1
  33. package/lib/icons/LinkIcon/LinkIconSymbol.d.ts +12 -0
  34. package/lib/icons/LinkIcon/LinkIconSymbol.js +41 -0
  35. package/lib/index.d.ts +1 -0
  36. package/lib/index.js +1 -0
  37. package/lib/markdoc/components/Cards/Card.js +1 -1
  38. package/lib/markdoc/components/Prompt/Prompt.d.ts +15 -0
  39. package/lib/markdoc/components/Prompt/Prompt.js +163 -0
  40. package/lib/markdoc/components/Prompt/PromptActions.d.ts +13 -0
  41. package/lib/markdoc/components/Prompt/PromptActions.js +43 -0
  42. package/lib/markdoc/components/Prompt/PromptBody.d.ts +14 -0
  43. package/lib/markdoc/components/Prompt/PromptBody.js +99 -0
  44. package/lib/markdoc/components/Prompt/PromptCopyButton.d.ts +10 -0
  45. package/lib/markdoc/components/Prompt/PromptCopyButton.js +28 -0
  46. package/lib/markdoc/components/Prompt/PromptHeader.d.ts +9 -0
  47. package/lib/markdoc/components/Prompt/PromptHeader.js +57 -0
  48. package/lib/markdoc/components/Prompt/PromptShowHideButton.d.ts +7 -0
  49. package/lib/markdoc/components/Prompt/PromptShowHideButton.js +21 -0
  50. package/lib/markdoc/components/Prompt/PromptToggleLabel.d.ts +7 -0
  51. package/lib/markdoc/components/Prompt/PromptToggleLabel.js +28 -0
  52. package/lib/markdoc/components/Prompt/variables.d.ts +1 -0
  53. package/lib/markdoc/components/Prompt/variables.dark.d.ts +1 -0
  54. package/lib/markdoc/components/Prompt/variables.dark.js +8 -0
  55. package/lib/markdoc/components/Prompt/variables.js +44 -0
  56. package/lib/markdoc/components/default.d.ts +1 -0
  57. package/lib/markdoc/components/default.js +1 -0
  58. package/lib/markdoc/default.d.ts +2 -0
  59. package/lib/markdoc/default.js +2 -0
  60. package/lib/markdoc/tags/code-walkthrough.d.ts +1 -1
  61. package/lib/markdoc/tags/code-walkthrough.js +3 -5
  62. package/lib/markdoc/tags/prompt.d.ts +2 -0
  63. package/lib/markdoc/tags/prompt.js +56 -0
  64. package/package.json +2 -7
  65. package/src/components/LoadProgressBar/LoadProgressBar.tsx +113 -0
  66. package/src/core/constants/index.ts +1 -0
  67. package/src/core/constants/prompt.ts +34 -0
  68. package/src/core/hooks/__mocks__/index.ts +3 -0
  69. package/src/core/hooks/__mocks__/use-theme-hooks.ts +2 -0
  70. package/src/core/hooks/index.ts +3 -0
  71. package/src/core/hooks/prompt/use-prompt-actions.ts +28 -0
  72. package/src/core/hooks/prompt/use-prompt-text-limit.ts +59 -0
  73. package/src/core/hooks/use-page-actions.ts +4 -9
  74. package/src/core/hooks/use-pointer-border-highlight.ts +46 -0
  75. package/src/core/hooks/use-telemetry-fallback.ts +2 -0
  76. package/src/core/styles/dark.ts +2 -0
  77. package/src/core/styles/global.ts +2 -40
  78. package/src/core/types/index.ts +1 -0
  79. package/src/core/types/l10n.ts +11 -0
  80. package/src/core/types/prompt.ts +11 -0
  81. package/src/core/utils/get-ai-prompt-link.ts +30 -0
  82. package/src/core/utils/index.ts +1 -0
  83. package/src/core/utils/urls.ts +1 -1
  84. package/src/core/utils/with-load-progress.ts +23 -18
  85. package/src/ext/configure.ts +1 -1
  86. package/src/icons/LinkIcon/LinkIconSymbol.tsx +51 -0
  87. package/src/index.ts +1 -0
  88. package/src/markdoc/components/Cards/Card.tsx +1 -1
  89. package/src/markdoc/components/Prompt/Prompt.tsx +219 -0
  90. package/src/markdoc/components/Prompt/PromptActions.tsx +104 -0
  91. package/src/markdoc/components/Prompt/PromptBody.tsx +107 -0
  92. package/src/markdoc/components/Prompt/PromptCopyButton.tsx +73 -0
  93. package/src/markdoc/components/Prompt/PromptHeader.tsx +81 -0
  94. package/src/markdoc/components/Prompt/PromptShowHideButton.tsx +43 -0
  95. package/src/markdoc/components/Prompt/PromptToggleLabel.tsx +41 -0
  96. package/src/markdoc/components/Prompt/variables.dark.ts +5 -0
  97. package/src/markdoc/components/Prompt/variables.ts +41 -0
  98. package/src/markdoc/components/default.ts +1 -0
  99. package/src/markdoc/default.ts +2 -0
  100. package/src/markdoc/tags/code-walkthrough.ts +4 -12
  101. package/src/markdoc/tags/prompt.ts +58 -0
@@ -0,0 +1,219 @@
1
+ import React, { useState } from 'react';
2
+ import styled, { css } from 'styled-components';
3
+
4
+ import type { JSX } from 'react';
5
+ import type { AiPromptTarget } from '@redocly/theme/core/types';
6
+
7
+ import { PROMPT_NARROW_BLOCK_MAX_WIDTH } from '@redocly/theme/core/constants';
8
+ import {
9
+ usePointerBorderHighlight,
10
+ usePromptActions,
11
+ usePromptTextLimit,
12
+ } from '@redocly/theme/core/hooks';
13
+ import { PromptActions } from '@redocly/theme/markdoc/components/Prompt/PromptActions';
14
+ import { PromptBody } from '@redocly/theme/markdoc/components/Prompt/PromptBody';
15
+ import { PromptShowHideButton } from '@redocly/theme/markdoc/components/Prompt/PromptShowHideButton';
16
+ import { PromptCopyButton } from '@redocly/theme/markdoc/components/Prompt/PromptCopyButton';
17
+ import { PromptHeader } from '@redocly/theme/markdoc/components/Prompt/PromptHeader';
18
+
19
+ export type PromptProps = {
20
+ /** Raw Markdown source of the prompt; rendered and copied as plain text. */
21
+ prompt: string;
22
+ title?: string;
23
+ icon?: string;
24
+ iconRawContent?: string;
25
+ actions?: AiPromptTarget[];
26
+ hidePrompt?: boolean;
27
+ singleLine?: boolean;
28
+ buttonOnly?: boolean;
29
+ className?: string;
30
+ };
31
+
32
+ export function Prompt(props: PromptProps): JSX.Element {
33
+ const {
34
+ prompt,
35
+ title,
36
+ icon,
37
+ iconRawContent,
38
+ actions = [],
39
+ hidePrompt = false,
40
+ singleLine = false,
41
+ buttonOnly = false,
42
+ className,
43
+ } = props;
44
+ const [isPromptVisible, setIsPromptVisible] = useState(!hidePrompt);
45
+ const { reportCopy, openIn } = usePromptActions(prompt);
46
+ const { textRef, textLimit, isExpanded, hasHiddenText, toggleExpanded } = usePromptTextLimit({
47
+ prompt,
48
+ singleLine,
49
+ isPromptVisible,
50
+ });
51
+ const borderHighlight = usePointerBorderHighlight<HTMLDivElement>();
52
+
53
+ if (buttonOnly) {
54
+ const copyButton = (
55
+ <PromptCopyButton prompt={prompt} placement="standalone" onCopy={reportCopy} />
56
+ );
57
+
58
+ return (
59
+ <PromptButtonOnlyWrapper data-component-name="Markdoc/Prompt/Prompt" className={className}>
60
+ {actions.length > 0 ? (
61
+ <PromptActions actions={actions} onOpen={openIn} button={copyButton} alignment="start" />
62
+ ) : (
63
+ copyButton
64
+ )}
65
+ </PromptButtonOnlyWrapper>
66
+ );
67
+ }
68
+
69
+ const hasHeader = Boolean(title) || Boolean(icon) || actions.length > 0 || hidePrompt;
70
+ const headerControls = (
71
+ <>
72
+ {hidePrompt ? (
73
+ <PromptShowHideButton
74
+ isPromptVisible={isPromptVisible}
75
+ onToggle={() => setIsPromptVisible(!isPromptVisible)}
76
+ />
77
+ ) : null}
78
+ <HeaderCopyWrapper $alwaysVisible={hidePrompt}>
79
+ <PromptCopyButton prompt={prompt} placement="header" onCopy={reportCopy} />
80
+ </HeaderCopyWrapper>
81
+ </>
82
+ );
83
+
84
+ return (
85
+ <PromptWrapper data-component-name="Markdoc/Prompt/Prompt" className={className}>
86
+ <PromptCardWrapper
87
+ ref={borderHighlight.ref}
88
+ onPointerMove={borderHighlight.onPointerMove}
89
+ onPointerLeave={borderHighlight.onPointerLeave}
90
+ >
91
+ {hasHeader ? (
92
+ <PromptHeader
93
+ title={title}
94
+ icon={icon}
95
+ iconRawContent={iconRawContent}
96
+ controls={headerControls}
97
+ />
98
+ ) : null}
99
+ {actions.length > 0 ? <HeaderActionsWrapper actions={actions} onOpen={openIn} /> : null}
100
+ {isPromptVisible ? (
101
+ <PromptBody
102
+ prompt={prompt}
103
+ textRef={textRef}
104
+ textLimit={textLimit}
105
+ isExpanded={isExpanded}
106
+ hasHiddenText={hasHiddenText}
107
+ onToggleExpanded={toggleExpanded}
108
+ controls={
109
+ hidePrompt ? null : (
110
+ <BodyCopyWrapper $hiddenWhenNarrow={hasHeader}>
111
+ <PromptCopyButton prompt={prompt} placement="body" onCopy={reportCopy} />
112
+ </BodyCopyWrapper>
113
+ )
114
+ }
115
+ />
116
+ ) : null}
117
+ </PromptCardWrapper>
118
+ </PromptWrapper>
119
+ );
120
+ }
121
+
122
+ /* The card cannot query its own width, so the container query sits on this wrapper. */
123
+ const PromptWrapper = styled.div`
124
+ container: prompt / inline-size;
125
+ margin: var(--prompt-margin);
126
+ `;
127
+
128
+ /* Below 440px the block is a phone screen or a narrow column: the actions get their own
129
+ full-width row under the header, and the copy button moves from the prompt zone to the
130
+ header. The DOM order (header, actions, prompt) is the same in both layouts. */
131
+ const NARROW_BLOCK = `@container prompt (width < ${PROMPT_NARROW_BLOCK_MAX_WIDTH})`;
132
+
133
+ const PromptCardWrapper = styled.div`
134
+ position: relative;
135
+ display: flex;
136
+ flex-wrap: wrap;
137
+ align-items: center;
138
+ row-gap: var(--prompt-gap);
139
+ column-gap: var(--prompt-header-actions-gap);
140
+ padding: var(--prompt-padding-vertical) var(--prompt-padding-horizontal);
141
+
142
+ > [data-component-name='Markdoc/Prompt/PromptHeader'] {
143
+ flex: 1 1 0;
144
+ min-width: 0;
145
+ }
146
+
147
+ > [data-component-name='Markdoc/Prompt/PromptBody'] {
148
+ flex: 1 1 100%;
149
+ }
150
+ background-color: var(--prompt-bg-color);
151
+ border: var(--prompt-border-width) var(--border-style) var(--prompt-border-color);
152
+ border-radius: var(--prompt-border-radius);
153
+
154
+ /* A gradient ring over the border, lit around the pointer. The mask keeps only the
155
+ border-width frame of the gradient; --pointer-* come from usePointerBorderHighlight. */
156
+ &::before {
157
+ content: '';
158
+ position: absolute;
159
+ inset: calc(-1 * var(--prompt-border-width));
160
+ padding: var(--prompt-border-width);
161
+ border-radius: var(--prompt-border-radius);
162
+ background: radial-gradient(
163
+ var(--prompt-border-highlight-size) circle at var(--pointer-x, 50%) var(--pointer-y, 50%),
164
+ var(--prompt-border-highlight-color-start),
165
+ var(--prompt-border-highlight-color-end) 40%,
166
+ transparent 80%
167
+ );
168
+ mask:
169
+ linear-gradient(currentColor 0 0) content-box,
170
+ linear-gradient(currentColor 0 0);
171
+ mask-composite: exclude;
172
+ opacity: var(--pointer-edge-proximity, 0);
173
+ transition: var(--prompt-border-highlight-transition);
174
+ pointer-events: none;
175
+ }
176
+ `;
177
+
178
+ const HeaderActionsWrapper = styled(PromptActions)`
179
+ flex: 0 0 auto;
180
+
181
+ ${NARROW_BLOCK} {
182
+ flex-basis: 100%;
183
+ display: flex;
184
+
185
+ [data-component-name='SplitButton/SplitButton'],
186
+ [data-component-name='Button/ButtonGroup'] {
187
+ width: 100%;
188
+ }
189
+
190
+ [data-component-name='Button/ButtonGroup'] > .button:first-child {
191
+ flex: 1;
192
+ }
193
+ }
194
+ `;
195
+
196
+ const HeaderCopyWrapper = styled.div<{ $alwaysVisible: boolean }>`
197
+ display: ${({ $alwaysVisible }) => ($alwaysVisible ? 'flex' : 'none')};
198
+
199
+ ${NARROW_BLOCK} {
200
+ display: flex;
201
+ }
202
+ `;
203
+
204
+ const BodyCopyWrapper = styled.div<{ $hiddenWhenNarrow: boolean }>`
205
+ display: flex;
206
+
207
+ ${({ $hiddenWhenNarrow }) =>
208
+ $hiddenWhenNarrow &&
209
+ css`
210
+ ${NARROW_BLOCK} {
211
+ display: none;
212
+ }
213
+ `}
214
+ `;
215
+
216
+ const PromptButtonOnlyWrapper = styled.div`
217
+ display: flex;
218
+ margin: var(--prompt-margin);
219
+ `;
@@ -0,0 +1,104 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { JSX, ReactElement } from 'react';
5
+ import type { ButtonProps } from '@redocly/theme/components/Button/Button';
6
+ import type { AiPromptTarget } from '@redocly/theme/core/types';
7
+
8
+ import { Button } from '@redocly/theme/components/Button/Button';
9
+ import { SplitButton } from '@redocly/theme/components/SplitButton/SplitButton';
10
+ import { DropdownMenu } from '@redocly/theme/components/Dropdown/DropdownMenu';
11
+ import { DropdownMenuItem } from '@redocly/theme/components/Dropdown/DropdownMenuItem';
12
+ import { DropdownMenuItemContent } from '@redocly/theme/components/Dropdown/DropdownMenuItemContent';
13
+ import { ChatGptIcon } from '@redocly/theme/icons/ChatGptIcon/ChatGptIcon';
14
+ import { ClaudeIcon } from '@redocly/theme/icons/ClaudeIcon/ClaudeIcon';
15
+ import { CursorIcon } from '@redocly/theme/icons/CursorIcon/CursorIcon';
16
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
17
+ import { AI_PROMPT_ACTION_OPTIONS } from '@redocly/theme/core/constants';
18
+
19
+ export type PromptActionsProps = {
20
+ actions: AiPromptTarget[];
21
+ onOpen: (target: AiPromptTarget) => void;
22
+ /** Main button of the split button; without it, the first action is the main button. */
23
+ button?: ReactElement<ButtonProps>;
24
+ /** Side of the split button the menu lines up with. */
25
+ alignment?: 'start' | 'end';
26
+ className?: string;
27
+ };
28
+
29
+ const ACTION_ICONS: Record<AiPromptTarget, React.ComponentType> = {
30
+ cursor: CursorIcon,
31
+ claude: ClaudeIcon,
32
+ chatgpt: ChatGptIcon,
33
+ };
34
+
35
+ export function PromptActions({
36
+ actions,
37
+ onOpen,
38
+ button,
39
+ alignment = 'end',
40
+ className,
41
+ }: PromptActionsProps): JSX.Element | null {
42
+ const { useTranslate } = useThemeHooks();
43
+ const { translate } = useTranslate();
44
+
45
+ const [firstAction, ...restActions] = actions;
46
+ if (!firstAction) return null;
47
+
48
+ const menuActions = button ? actions : restActions;
49
+ const primaryButton = button ?? (
50
+ <Button
51
+ variant="secondary"
52
+ size="small"
53
+ icon={<PromptActionIcon action={firstAction} />}
54
+ onClick={() => onOpen(firstAction)}
55
+ >
56
+ {translate(
57
+ AI_PROMPT_ACTION_OPTIONS[firstAction].titleTranslationKey,
58
+ AI_PROMPT_ACTION_OPTIONS[firstAction].titleDefault,
59
+ )}
60
+ </Button>
61
+ );
62
+
63
+ return (
64
+ <PromptActionsWrapper data-component-name="Markdoc/Prompt/PromptActions" className={className}>
65
+ <SplitButton
66
+ variant="secondary"
67
+ size="small"
68
+ alignment={alignment}
69
+ portalled
70
+ toggleAriaLabel={translate('page.actions.moreActions', 'More actions')}
71
+ button={primaryButton}
72
+ >
73
+ {menuActions.length > 0 ? (
74
+ <DropdownMenu>
75
+ {menuActions.map((action) => (
76
+ <DropdownMenuItem key={action} onAction={() => onOpen(action)}>
77
+ <DropdownMenuItemContent
78
+ icon={<PromptActionIcon action={action} />}
79
+ title={translate(
80
+ AI_PROMPT_ACTION_OPTIONS[action].titleTranslationKey,
81
+ AI_PROMPT_ACTION_OPTIONS[action].titleDefault,
82
+ )}
83
+ description={translate(
84
+ AI_PROMPT_ACTION_OPTIONS[action].descriptionTranslationKey,
85
+ AI_PROMPT_ACTION_OPTIONS[action].descriptionDefault,
86
+ )}
87
+ />
88
+ </DropdownMenuItem>
89
+ ))}
90
+ </DropdownMenu>
91
+ ) : null}
92
+ </SplitButton>
93
+ </PromptActionsWrapper>
94
+ );
95
+ }
96
+
97
+ function PromptActionIcon({ action }: { action: AiPromptTarget }): JSX.Element {
98
+ const Icon = ACTION_ICONS[action];
99
+ return <Icon />;
100
+ }
101
+
102
+ const PromptActionsWrapper = styled.div`
103
+ display: inline-flex;
104
+ `;
@@ -0,0 +1,107 @@
1
+ import React from 'react';
2
+ import styled, { css } from 'styled-components';
3
+
4
+ import type { JSX, ReactNode, RefObject } from 'react';
5
+ import type { PromptTextLimit } from '@redocly/theme/core/hooks';
6
+
7
+ import { Button } from '@redocly/theme/components/Button/Button';
8
+ import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon';
9
+ import { ChevronUpIcon } from '@redocly/theme/icons/ChevronUpIcon/ChevronUpIcon';
10
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
11
+
12
+ export type PromptBodyProps = {
13
+ prompt: string;
14
+ textRef: RefObject<HTMLDivElement | null>;
15
+ textLimit: PromptTextLimit;
16
+ isExpanded: boolean;
17
+ hasHiddenText: boolean;
18
+ onToggleExpanded: () => void;
19
+ /** Shown inside the prompt zone; omitted when the header owns the copy button. */
20
+ controls?: ReactNode;
21
+ className?: string;
22
+ };
23
+
24
+ export function PromptBody({
25
+ prompt,
26
+ textRef,
27
+ textLimit,
28
+ isExpanded,
29
+ hasHiddenText,
30
+ onToggleExpanded,
31
+ controls,
32
+ className,
33
+ }: PromptBodyProps): JSX.Element {
34
+ const { useTranslate } = useThemeHooks();
35
+ const { translate } = useTranslate();
36
+
37
+ return (
38
+ <PromptBodyWrapper data-component-name="Markdoc/Prompt/PromptBody" className={className}>
39
+ <ZoneWrapper>
40
+ <TextWrapper ref={textRef} $textLimit={textLimit}>
41
+ {prompt}
42
+ </TextWrapper>
43
+ {controls}
44
+ </ZoneWrapper>
45
+ {hasHiddenText || isExpanded ? (
46
+ <Button
47
+ variant="ghost"
48
+ size="small"
49
+ icon={isExpanded ? <ChevronUpIcon /> : <ChevronDownIcon />}
50
+ iconPosition="right"
51
+ onClick={onToggleExpanded}
52
+ >
53
+ {isExpanded
54
+ ? translate('promptTag.showLess', 'Show less')
55
+ : translate('promptTag.showFullPrompt', 'Show full prompt')}
56
+ </Button>
57
+ ) : null}
58
+ </PromptBodyWrapper>
59
+ );
60
+ }
61
+
62
+ const PromptBodyWrapper = styled.div`
63
+ display: flex;
64
+ flex-direction: column;
65
+ align-items: flex-start;
66
+ gap: var(--prompt-body-toggle-gap);
67
+ `;
68
+
69
+ const ZoneWrapper = styled.div`
70
+ display: flex;
71
+ align-items: flex-start;
72
+ width: 100%;
73
+ gap: var(--prompt-body-gap);
74
+ padding: var(--prompt-body-padding-vertical) var(--prompt-body-padding-horizontal);
75
+ background-color: var(--prompt-body-bg-color);
76
+ border-radius: var(--prompt-body-border-radius);
77
+ `;
78
+
79
+ /* width: 0 keeps the nowrap text from widening the block; flex-grow gives the text the real width. */
80
+ const TextWrapper = styled.div<{ $textLimit: PromptTextLimit }>`
81
+ flex: 1 1 0;
82
+ width: 0;
83
+ min-width: 0;
84
+ white-space: pre-wrap;
85
+ overflow: visible;
86
+ overflow-wrap: anywhere;
87
+ font-size: var(--prompt-body-font-size);
88
+ line-height: var(--prompt-body-line-height);
89
+ color: var(--prompt-body-text-color);
90
+
91
+ ${({ $textLimit }) =>
92
+ $textLimit === 'maxLines' &&
93
+ css`
94
+ display: -webkit-box;
95
+ -webkit-box-orient: vertical;
96
+ -webkit-line-clamp: var(--prompt-body-max-lines);
97
+ overflow: hidden;
98
+ `}
99
+
100
+ ${({ $textLimit }) =>
101
+ $textLimit === 'oneLine' &&
102
+ css`
103
+ white-space: nowrap;
104
+ overflow: hidden;
105
+ text-overflow: ellipsis;
106
+ `}
107
+ `;
@@ -0,0 +1,73 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { JSX } from 'react';
5
+
6
+ import { CopyButton } from '@redocly/theme/components/Buttons/CopyButton';
7
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
8
+
9
+ export type PromptCopyButtonPlacement = 'header' | 'body' | 'standalone';
10
+
11
+ export type PromptCopyButtonProps = {
12
+ prompt: string;
13
+ /** `header` next to the split button, `body` inside the prompt zone, `standalone` for the button-only variant. */
14
+ placement: PromptCopyButtonPlacement;
15
+ onCopy: () => void;
16
+ className?: string;
17
+ };
18
+
19
+ export function PromptCopyButton({
20
+ prompt,
21
+ placement,
22
+ onCopy,
23
+ className,
24
+ }: PromptCopyButtonProps): JSX.Element {
25
+ const { useTranslate } = useThemeHooks();
26
+ const { translate } = useTranslate();
27
+
28
+ if (placement === 'standalone') {
29
+ return (
30
+ <CopyButton
31
+ data={prompt}
32
+ type="compound"
33
+ variant="secondary"
34
+ size="small"
35
+ buttonText={translate('promptTag.copy.buttonText', 'Copy prompt')}
36
+ onCopyClick={onCopy}
37
+ dataTestId="prompt-copy-button"
38
+ className={className}
39
+ />
40
+ );
41
+ }
42
+
43
+ if (placement === 'header') {
44
+ return (
45
+ <HeaderCopyButtonWrapper
46
+ data={prompt}
47
+ variant="outlined"
48
+ size="small"
49
+ onCopyClick={onCopy}
50
+ dataTestId="prompt-header-copy-button"
51
+ className={className}
52
+ />
53
+ );
54
+ }
55
+
56
+ return (
57
+ <CopyButton
58
+ data={prompt}
59
+ variant="text"
60
+ size="small"
61
+ onCopyClick={onCopy}
62
+ dataTestId="prompt-copy-button"
63
+ className={className}
64
+ />
65
+ );
66
+ }
67
+
68
+ /* The outlined border adds to the box; the smaller padding keeps the 24px height of the sibling buttons. */
69
+ const HeaderCopyButtonWrapper = styled(CopyButton)`
70
+ && .button {
71
+ --button-icon-padding: var(--prompt-header-copy-button-padding);
72
+ }
73
+ `;
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { JSX, ReactNode } from 'react';
5
+
6
+ import { AiStarsIcon } from '@redocly/theme/icons/AiStarsIcon/AiStarsIcon';
7
+ import { GenericIcon } from '@redocly/theme/icons/GenericIcon/GenericIcon';
8
+
9
+ export type PromptHeaderProps = {
10
+ title?: string;
11
+ icon?: string;
12
+ iconRawContent?: string;
13
+ controls?: ReactNode;
14
+ className?: string;
15
+ };
16
+
17
+ export function PromptHeader({
18
+ title,
19
+ icon,
20
+ iconRawContent,
21
+ controls,
22
+ className,
23
+ }: PromptHeaderProps): JSX.Element {
24
+ return (
25
+ <PromptHeaderWrapper data-component-name="Markdoc/Prompt/PromptHeader" className={className}>
26
+ <IconWrapper>
27
+ {icon ? (
28
+ <GenericIcon icon={icon} rawContent={iconRawContent} />
29
+ ) : (
30
+ <AiStarsIcon color="--prompt-icon-color" />
31
+ )}
32
+ </IconWrapper>
33
+ {title ? <TitleWrapper>{title}</TitleWrapper> : null}
34
+ {controls ? <ControlsWrapper>{controls}</ControlsWrapper> : null}
35
+ </PromptHeaderWrapper>
36
+ );
37
+ }
38
+
39
+ const PromptHeaderWrapper = styled.div`
40
+ display: flex;
41
+ align-items: center;
42
+ gap: var(--prompt-header-gap);
43
+ `;
44
+
45
+ const IconWrapper = styled.div`
46
+ display: flex;
47
+ align-items: center;
48
+ justify-content: center;
49
+ flex-shrink: 0;
50
+ width: var(--prompt-icon-size);
51
+ height: var(--prompt-icon-size);
52
+ color: var(--prompt-icon-color);
53
+ --icon-width: var(--prompt-icon-size);
54
+ --icon-height: var(--prompt-icon-size);
55
+ --icon-color: var(--prompt-icon-color);
56
+
57
+ svg {
58
+ width: 100%;
59
+ height: 100%;
60
+ }
61
+ `;
62
+
63
+ const TitleWrapper = styled.div`
64
+ flex: 1;
65
+ min-width: 0;
66
+ overflow: hidden;
67
+ text-overflow: ellipsis;
68
+ white-space: nowrap;
69
+ font-size: var(--prompt-title-font-size);
70
+ font-weight: var(--prompt-title-font-weight);
71
+ line-height: var(--prompt-title-line-height);
72
+ color: var(--prompt-title-text-color);
73
+ `;
74
+
75
+ const ControlsWrapper = styled.div`
76
+ display: flex;
77
+ align-items: center;
78
+ flex-shrink: 0;
79
+ margin-left: auto;
80
+ gap: var(--prompt-header-actions-gap);
81
+ `;
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+
3
+ import type { JSX } from 'react';
4
+
5
+ import { Button } from '@redocly/theme/components/Button/Button';
6
+ import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon';
7
+ import { ChevronUpIcon } from '@redocly/theme/icons/ChevronUpIcon/ChevronUpIcon';
8
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
9
+ import { PromptToggleLabel } from '@redocly/theme/markdoc/components/Prompt/PromptToggleLabel';
10
+
11
+ export type PromptShowHideButtonProps = {
12
+ isPromptVisible: boolean;
13
+ onToggle: () => void;
14
+ className?: string;
15
+ };
16
+
17
+ export function PromptShowHideButton({
18
+ isPromptVisible,
19
+ onToggle,
20
+ className,
21
+ }: PromptShowHideButtonProps): JSX.Element {
22
+ const { useTranslate } = useThemeHooks();
23
+ const { translate } = useTranslate();
24
+ const showPromptLabel = translate('promptTag.showPrompt', 'Show prompt');
25
+ const hidePromptLabel = translate('promptTag.hidePrompt', 'Hide prompt');
26
+
27
+ return (
28
+ <Button
29
+ data-component-name="Markdoc/Prompt/PromptShowHideButton"
30
+ className={className}
31
+ variant="text"
32
+ size="small"
33
+ icon={isPromptVisible ? <ChevronUpIcon /> : <ChevronDownIcon />}
34
+ iconPosition="right"
35
+ onClick={onToggle}
36
+ >
37
+ <PromptToggleLabel
38
+ active={isPromptVisible ? hidePromptLabel : showPromptLabel}
39
+ inactive={isPromptVisible ? showPromptLabel : hidePromptLabel}
40
+ />
41
+ </Button>
42
+ );
43
+ }
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { JSX } from 'react';
5
+
6
+ export type PromptToggleLabelProps = {
7
+ active: string;
8
+ inactive: string;
9
+ className?: string;
10
+ };
11
+
12
+ /* Both labels share one grid cell, so the toggle keeps the width of the longer label
13
+ and the buttons next to it do not move when the label changes. */
14
+ export function PromptToggleLabel({
15
+ active,
16
+ inactive,
17
+ className,
18
+ }: PromptToggleLabelProps): JSX.Element {
19
+ return (
20
+ <PromptToggleLabelWrapper
21
+ data-component-name="Markdoc/Prompt/PromptToggleLabel"
22
+ className={className}
23
+ >
24
+ <span>{active}</span>
25
+ <span aria-hidden="true">{inactive}</span>
26
+ </PromptToggleLabelWrapper>
27
+ );
28
+ }
29
+
30
+ const PromptToggleLabelWrapper = styled.span`
31
+ display: grid;
32
+ text-align: center;
33
+
34
+ > span {
35
+ grid-area: 1 / 1;
36
+ }
37
+
38
+ > span[aria-hidden='true'] {
39
+ visibility: hidden;
40
+ }
41
+ `;
@@ -0,0 +1,5 @@
1
+ import { css } from 'styled-components';
2
+
3
+ export const promptTagDarkMode = css`
4
+ --prompt-body-bg-color: var(--color-brand-subtle-bg, var(--color-warm-grey-3)); // @presenter Color
5
+ `;