@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.
- package/lib/components/LoadProgressBar/LoadProgressBar.d.ts +8 -0
- package/lib/components/LoadProgressBar/LoadProgressBar.js +122 -0
- package/lib/core/constants/index.d.ts +1 -0
- package/lib/core/constants/index.js +1 -0
- package/lib/core/constants/prompt.d.ts +9 -0
- package/lib/core/constants/prompt.js +33 -0
- package/lib/core/hooks/index.d.ts +3 -0
- package/lib/core/hooks/index.js +3 -0
- package/lib/core/hooks/prompt/use-prompt-actions.d.ts +7 -0
- package/lib/core/hooks/prompt/use-prompt-actions.js +19 -0
- package/lib/core/hooks/prompt/use-prompt-text-limit.d.ts +19 -0
- package/lib/core/hooks/prompt/use-prompt-text-limit.js +35 -0
- package/lib/core/hooks/use-page-actions.js +4 -8
- package/lib/core/hooks/use-pointer-border-highlight.d.ts +7 -0
- package/lib/core/hooks/use-pointer-border-highlight.js +29 -0
- package/lib/core/hooks/use-telemetry-fallback.d.ts +2 -0
- package/lib/core/hooks/use-telemetry-fallback.js +2 -0
- package/lib/core/styles/dark.js +14 -12
- package/lib/core/styles/global.js +16 -54
- package/lib/core/types/index.d.ts +1 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/types/prompt.d.ts +9 -0
- package/lib/core/types/prompt.js +3 -0
- package/lib/core/utils/get-ai-prompt-link.d.ts +15 -0
- package/lib/core/utils/get-ai-prompt-link.js +29 -0
- package/lib/core/utils/index.d.ts +1 -0
- package/lib/core/utils/index.js +1 -0
- package/lib/core/utils/urls.js +1 -1
- package/lib/core/utils/with-load-progress.d.ts +3 -0
- package/lib/core/utils/with-load-progress.js +24 -19
- package/lib/ext/configure.d.ts +1 -1
- package/lib/ext/configure.js +0 -1
- package/lib/icons/LinkIcon/LinkIconSymbol.d.ts +12 -0
- package/lib/icons/LinkIcon/LinkIconSymbol.js +41 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/markdoc/components/Cards/Card.js +1 -1
- package/lib/markdoc/components/Prompt/Prompt.d.ts +15 -0
- package/lib/markdoc/components/Prompt/Prompt.js +163 -0
- package/lib/markdoc/components/Prompt/PromptActions.d.ts +13 -0
- package/lib/markdoc/components/Prompt/PromptActions.js +43 -0
- package/lib/markdoc/components/Prompt/PromptBody.d.ts +14 -0
- package/lib/markdoc/components/Prompt/PromptBody.js +99 -0
- package/lib/markdoc/components/Prompt/PromptCopyButton.d.ts +10 -0
- package/lib/markdoc/components/Prompt/PromptCopyButton.js +28 -0
- package/lib/markdoc/components/Prompt/PromptHeader.d.ts +9 -0
- package/lib/markdoc/components/Prompt/PromptHeader.js +57 -0
- package/lib/markdoc/components/Prompt/PromptShowHideButton.d.ts +7 -0
- package/lib/markdoc/components/Prompt/PromptShowHideButton.js +21 -0
- package/lib/markdoc/components/Prompt/PromptToggleLabel.d.ts +7 -0
- package/lib/markdoc/components/Prompt/PromptToggleLabel.js +28 -0
- package/lib/markdoc/components/Prompt/variables.d.ts +1 -0
- package/lib/markdoc/components/Prompt/variables.dark.d.ts +1 -0
- package/lib/markdoc/components/Prompt/variables.dark.js +8 -0
- package/lib/markdoc/components/Prompt/variables.js +44 -0
- package/lib/markdoc/components/default.d.ts +1 -0
- package/lib/markdoc/components/default.js +1 -0
- package/lib/markdoc/default.d.ts +2 -0
- package/lib/markdoc/default.js +2 -0
- package/lib/markdoc/tags/code-walkthrough.d.ts +1 -1
- package/lib/markdoc/tags/code-walkthrough.js +3 -5
- package/lib/markdoc/tags/prompt.d.ts +2 -0
- package/lib/markdoc/tags/prompt.js +56 -0
- package/package.json +2 -7
- package/src/components/LoadProgressBar/LoadProgressBar.tsx +113 -0
- package/src/core/constants/index.ts +1 -0
- package/src/core/constants/prompt.ts +34 -0
- package/src/core/hooks/__mocks__/index.ts +3 -0
- package/src/core/hooks/__mocks__/use-theme-hooks.ts +2 -0
- package/src/core/hooks/index.ts +3 -0
- package/src/core/hooks/prompt/use-prompt-actions.ts +28 -0
- package/src/core/hooks/prompt/use-prompt-text-limit.ts +59 -0
- package/src/core/hooks/use-page-actions.ts +4 -9
- package/src/core/hooks/use-pointer-border-highlight.ts +46 -0
- package/src/core/hooks/use-telemetry-fallback.ts +2 -0
- package/src/core/styles/dark.ts +2 -0
- package/src/core/styles/global.ts +2 -40
- package/src/core/types/index.ts +1 -0
- package/src/core/types/l10n.ts +11 -0
- package/src/core/types/prompt.ts +11 -0
- package/src/core/utils/get-ai-prompt-link.ts +30 -0
- package/src/core/utils/index.ts +1 -0
- package/src/core/utils/urls.ts +1 -1
- package/src/core/utils/with-load-progress.ts +23 -18
- package/src/ext/configure.ts +1 -1
- package/src/icons/LinkIcon/LinkIconSymbol.tsx +51 -0
- package/src/index.ts +1 -0
- package/src/markdoc/components/Cards/Card.tsx +1 -1
- package/src/markdoc/components/Prompt/Prompt.tsx +219 -0
- package/src/markdoc/components/Prompt/PromptActions.tsx +104 -0
- package/src/markdoc/components/Prompt/PromptBody.tsx +107 -0
- package/src/markdoc/components/Prompt/PromptCopyButton.tsx +73 -0
- package/src/markdoc/components/Prompt/PromptHeader.tsx +81 -0
- package/src/markdoc/components/Prompt/PromptShowHideButton.tsx +43 -0
- package/src/markdoc/components/Prompt/PromptToggleLabel.tsx +41 -0
- package/src/markdoc/components/Prompt/variables.dark.ts +5 -0
- package/src/markdoc/components/Prompt/variables.ts +41 -0
- package/src/markdoc/components/default.ts +1 -0
- package/src/markdoc/default.ts +2 -0
- package/src/markdoc/tags/code-walkthrough.ts +4 -12
- 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
|
+
`;
|