cherry-styled-components 0.2.11 → 0.2.13
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/README.md +89 -28
- package/dist/avatar-dropzone.js +8 -8
- package/dist/avatar.d.ts +17 -0
- package/dist/avatar.js +68 -0
- package/dist/button.js +15 -12
- package/dist/callout.d.ts +13 -0
- package/dist/callout.js +121 -0
- package/dist/chat-input.d.ts +18 -0
- package/dist/chat-input.js +257 -0
- package/dist/chat-launcher.d.ts +10 -0
- package/dist/chat-launcher.js +78 -0
- package/dist/chat-message-list.d.ts +12 -0
- package/dist/chat-message-list.js +88 -0
- package/dist/chat-message.d.ts +16 -0
- package/dist/chat-message.js +56 -0
- package/dist/chat-panel.d.ts +25 -0
- package/dist/chat-panel.js +163 -0
- package/dist/chat-provider.d.ts +86 -0
- package/dist/chat-provider.js +295 -0
- package/dist/chat-showcase.d.ts +18 -0
- package/dist/chat-showcase.js +234 -0
- package/dist/chat-sources.d.ts +10 -0
- package/dist/chat-sources.js +41 -0
- package/dist/chat-typing.d.ts +7 -0
- package/dist/chat-typing.js +44 -0
- package/dist/dropzone.js +14 -14
- package/dist/icon-button.js +7 -7
- package/dist/index.d.ts +13 -0
- package/dist/index.js +18 -3
- package/dist/input.js +6 -6
- package/dist/modal.js +6 -6
- package/dist/prose.d.ts +17 -0
- package/dist/prose.js +75 -0
- package/dist/spinner.d.ts +11 -0
- package/dist/spinner.js +21 -0
- package/dist/styled-components/client-theme-provider.js +13 -3
- package/dist/tabs.js +6 -7
- package/dist/theme-toggle.js +5 -5
- package/dist/toast.d.ts +2 -2
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/mixins.d.ts +30 -0
- package/dist/utils/mixins.js +24 -3
- package/dist/utils/use-lock-body-scroll.d.ts +5 -0
- package/dist/utils/use-lock-body-scroll.js +25 -0
- package/dist/utils/use-media-query.d.ts +14 -0
- package/dist/utils/use-media-query.js +28 -0
- package/package.json +15 -14
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
import { alpha, formElementHeightStyles } from "./utils/mixins.js";
|
|
4
|
+
import { Icon } from "./icon.js";
|
|
5
|
+
import { Button } from "./button.js";
|
|
6
|
+
import { ChatContext } from "./chat-provider.js";
|
|
7
|
+
import { Spinner } from "./spinner.js";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { forwardRef, useContext, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
10
|
+
import styled, { createGlobalStyle, css, keyframes } from "styled-components";
|
|
11
|
+
//#region src/lib/chat-input.tsx
|
|
12
|
+
var GLOW_COLORS = [
|
|
13
|
+
"#cc5555",
|
|
14
|
+
"#d9a745",
|
|
15
|
+
"#3ab0cc",
|
|
16
|
+
"#cc7fc2",
|
|
17
|
+
"#4380cc",
|
|
18
|
+
"#4c1fa3"
|
|
19
|
+
];
|
|
20
|
+
var SPARKLE_COLORS = [
|
|
21
|
+
"#ff6b6b",
|
|
22
|
+
"#feca57",
|
|
23
|
+
"#48dbfb",
|
|
24
|
+
"#ff9ff3",
|
|
25
|
+
"#54a0ff",
|
|
26
|
+
"#5f27cd"
|
|
27
|
+
];
|
|
28
|
+
var SPARKLE_POSITIONS = [
|
|
29
|
+
{
|
|
30
|
+
left: 8,
|
|
31
|
+
top: 35
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
left: 17,
|
|
35
|
+
top: 55
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
left: 26,
|
|
39
|
+
top: 28
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
left: 35,
|
|
43
|
+
top: 68
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
left: 44,
|
|
47
|
+
top: 42
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
left: 53,
|
|
51
|
+
top: 75
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
left: 62,
|
|
55
|
+
top: 32
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
left: 71,
|
|
59
|
+
top: 58
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
left: 80,
|
|
63
|
+
top: 45
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
left: 89,
|
|
67
|
+
top: 65
|
|
68
|
+
}
|
|
69
|
+
];
|
|
70
|
+
var MAX_TEXTAREA_HEIGHT = 160;
|
|
71
|
+
var GradientAngleProperty = createGlobalStyle([`@property --cherry-gradient-angle{syntax:"<angle>";initial-value:0deg;inherits:false;}`]);
|
|
72
|
+
var rotateGradient = keyframes([`0%{--cherry-gradient-angle:0deg;}100%{--cherry-gradient-angle:360deg;}`]);
|
|
73
|
+
var pulseGlow = keyframes([`0%,100%{opacity:0.5;filter:blur(16px);}50%{opacity:1;filter:blur(22px);}`]);
|
|
74
|
+
var sparkleFloat = keyframes([`0%,100%{opacity:0;transform:translateY(0) scale(0);}50%{opacity:0.9;transform:translateY(-20px) scale(1);}`]);
|
|
75
|
+
var conicStops = (colors) => [...colors, colors[0]].join(", ");
|
|
76
|
+
var StyledChatForm = styled.form.withConfig({
|
|
77
|
+
displayName: "chat-input__StyledChatForm",
|
|
78
|
+
componentId: "sc-d9366e15-0"
|
|
79
|
+
})([
|
|
80
|
+
`display:flex;gap:10px;justify-content:center;align-items:flex-end;background:`,
|
|
81
|
+
`;padding:20px;width:100%;flex-shrink:0;border-top:solid 1px `,
|
|
82
|
+
`;`
|
|
83
|
+
], ({ theme }) => theme.colors.light, ({ theme }) => theme.colors.grayLight);
|
|
84
|
+
var StyledInputWrapper = styled.div.withConfig({
|
|
85
|
+
displayName: "chat-input__StyledInputWrapper",
|
|
86
|
+
componentId: "sc-d9366e15-1"
|
|
87
|
+
})([`position:relative;flex:1;min-width:0;`, ``], ({ $glow, $glowColors = GLOW_COLORS, theme }) => $glow && css([
|
|
88
|
+
`isolation:isolate;&::before{content:"";position:absolute;inset:0;border-radius:`,
|
|
89
|
+
`;background:conic-gradient( from var(--cherry-gradient-angle),`,
|
|
90
|
+
` );opacity:0;transition:all 0.3s ease;animation:`,
|
|
91
|
+
` 3s linear infinite;z-index:0;}&::after{content:"";position:absolute;inset:0;border-radius:`,
|
|
92
|
+
`;background:conic-gradient( from var(--cherry-gradient-angle),`,
|
|
93
|
+
` );opacity:0;transition:all 0.3s ease;animation:`,
|
|
94
|
+
` 3s linear infinite;z-index:-1;pointer-events:none;}&:hover::before,&:focus-within::before{opacity:1;}&:focus-within::after{opacity:1;inset:-4px;border-radius:calc(`,
|
|
95
|
+
` + 4px);}&:active::after{inset:-2px;border-radius:calc(`,
|
|
96
|
+
` + 2px);}@media (prefers-reduced-motion:reduce){&::before,&::after{animation:none;}}`
|
|
97
|
+
], theme.spacing.radius.xs, conicStops($glowColors), rotateGradient, theme.spacing.radius.xs, conicStops($glowColors.map((color) => alpha(color, 40))), rotateGradient, theme.spacing.radius.xs, theme.spacing.radius.xs));
|
|
98
|
+
var StyledRadiantGlow = styled.div.withConfig({
|
|
99
|
+
displayName: "chat-input__StyledRadiantGlow",
|
|
100
|
+
componentId: "sc-d9366e15-2"
|
|
101
|
+
})([
|
|
102
|
+
`position:absolute;inset:-10px;border-radius:20px;pointer-events:none;z-index:-2;opacity:0.6;transition:all 0.3s ease;&::before{content:"";position:absolute;inset:0;border-radius:inherit;background:conic-gradient( from var(--cherry-gradient-angle),`,
|
|
103
|
+
` );animation:`,
|
|
104
|
+
` 3s linear infinite,`,
|
|
105
|
+
` 2s ease-in-out infinite;}`,
|
|
106
|
+
` @media (prefers-reduced-motion:reduce){&::before{animation:none;}}`
|
|
107
|
+
], ({ $glowColors = GLOW_COLORS }) => conicStops($glowColors.map((color) => alpha(color, 40))), rotateGradient, pulseGlow, ({ $isActive }) => $isActive && css([`opacity:1;`]));
|
|
108
|
+
var StyledSparkleContainer = styled.div.withConfig({
|
|
109
|
+
displayName: "chat-input__StyledSparkleContainer",
|
|
110
|
+
componentId: "sc-d9366e15-3"
|
|
111
|
+
})([`position:absolute;inset:-30px;pointer-events:none;overflow:hidden;border-radius:30px;z-index:-3;opacity:0;transition:all 0.3s ease;`, ` @media (prefers-reduced-motion:reduce){display:none;}`], ({ $isActive }) => $isActive && css([`opacity:1;`]));
|
|
112
|
+
var StyledSparkle = styled.div.withConfig({
|
|
113
|
+
displayName: "chat-input__StyledSparkle",
|
|
114
|
+
componentId: "sc-d9366e15-4"
|
|
115
|
+
})([
|
|
116
|
+
`position:absolute;width:4px;height:4px;border-radius:50%;background:`,
|
|
117
|
+
`;box-shadow:0 0 6px `,
|
|
118
|
+
`;left:`,
|
|
119
|
+
`%;top:`,
|
|
120
|
+
`%;animation:`,
|
|
121
|
+
` 2s ease-in-out infinite;animation-delay:`,
|
|
122
|
+
`s;`
|
|
123
|
+
], ({ $color }) => $color, ({ $color }) => $color, ({ $left }) => $left, ({ $top }) => $top, sparkleFloat, ({ $delay }) => $delay);
|
|
124
|
+
var StyledTextarea = styled.textarea.withConfig({
|
|
125
|
+
displayName: "chat-input__StyledTextarea",
|
|
126
|
+
componentId: "sc-d9366e15-5"
|
|
127
|
+
})([
|
|
128
|
+
`position:relative;z-index:1;display:block;box-sizing:border-box;width:100%;resize:none;font-family:inherit;line-height:1.5;color:`,
|
|
129
|
+
`;background:`,
|
|
130
|
+
`;outline:none;max-height:`,
|
|
131
|
+
`px;overflow-y:auto;border:solid 2px `,
|
|
132
|
+
`;border-radius:`,
|
|
133
|
+
`;padding:11px 15px;font-size:`,
|
|
134
|
+
`;transition:border-color 0.3s ease,box-shadow 0.3s ease,background 0.3s ease;&::placeholder{color:`,
|
|
135
|
+
`;}&:disabled{cursor:not-allowed;background:`,
|
|
136
|
+
`;border-color:`,
|
|
137
|
+
`;color:`,
|
|
138
|
+
`;}`,
|
|
139
|
+
``
|
|
140
|
+
], ({ theme }) => theme.colors.dark, ({ theme }) => theme.colors.light, MAX_TEXTAREA_HEIGHT, ({ theme }) => theme.colors.grayLight, ({ theme }) => theme.spacing.radius.xs, ({ theme }) => theme.fontSizes.input.lg, ({ theme }) => theme.colors.gray, ({ theme }) => theme.colors.grayLight, ({ theme }) => theme.colors.gray, ({ theme }) => theme.colors.gray, ({ $glow, theme }) => $glow ? css([`background-clip:padding-box;&:hover:not([disabled]),&:focus:not([disabled]){border-color:transparent;}`]) : css([
|
|
141
|
+
`box-shadow:0 0 0 0px `,
|
|
142
|
+
`;&:hover:not([disabled]){border-color:`,
|
|
143
|
+
`;}&:focus:not([disabled]){box-shadow:0 0 0 4px `,
|
|
144
|
+
`;border-color:`,
|
|
145
|
+
`;}&:active:not([disabled]){box-shadow:0 0 0 2px `,
|
|
146
|
+
`;}`
|
|
147
|
+
], theme.colors.primaryLight, theme.colors.primary, theme.colors.primaryLight, theme.colors.primary, theme.colors.primaryLight));
|
|
148
|
+
var StyledSendButton = styled(Button).withConfig({
|
|
149
|
+
displayName: "chat-input__StyledSendButton",
|
|
150
|
+
componentId: "sc-d9366e15-6"
|
|
151
|
+
})([`flex-shrink:0;`, `;min-height:0;position:relative;& svg,& .lucide{transition:none;position:absolute;inset:0;margin:auto;}`], formElementHeightStyles());
|
|
152
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
153
|
+
function LocalChatInput({ value, onValueChange, onSend, $loading, $glow = false, $glowColors, $maxRows, placeholder = "Ask AI Assistant...", "aria-label": ariaLabel = "Ask a question", ...props }, ref) {
|
|
154
|
+
const chat = useContext(ChatContext);
|
|
155
|
+
const textareaRef = useRef(null);
|
|
156
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
157
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
158
|
+
const currentValue = value ?? chat.input;
|
|
159
|
+
const setValue = onValueChange ?? chat.setInput;
|
|
160
|
+
const loading = $loading ?? chat.loading;
|
|
161
|
+
const hasContent = currentValue.trim().length > 0;
|
|
162
|
+
const isActive = isFocused || isHovered;
|
|
163
|
+
const maxHeight = $maxRows ? $maxRows * 24 + 24 : MAX_TEXTAREA_HEIGHT;
|
|
164
|
+
useIsomorphicLayoutEffect(() => {
|
|
165
|
+
const textarea = textareaRef.current;
|
|
166
|
+
if (!textarea) return;
|
|
167
|
+
textarea.style.height = "auto";
|
|
168
|
+
const borders = textarea.offsetHeight - textarea.clientHeight;
|
|
169
|
+
textarea.style.height = `${Math.min(textarea.scrollHeight + borders, maxHeight)}px`;
|
|
170
|
+
}, [currentValue, maxHeight]);
|
|
171
|
+
const submit = () => {
|
|
172
|
+
if (loading || !hasContent) return;
|
|
173
|
+
if (onSend) onSend(currentValue);
|
|
174
|
+
else chat.send(currentValue);
|
|
175
|
+
};
|
|
176
|
+
const handleKeyDown = (event) => {
|
|
177
|
+
props.onKeyDown?.(event);
|
|
178
|
+
if (event.defaultPrevented) return;
|
|
179
|
+
if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
|
|
180
|
+
event.preventDefault();
|
|
181
|
+
submit();
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
const sparkles = SPARKLE_POSITIONS.map((position, index) => ({
|
|
185
|
+
color: ($glowColors ?? SPARKLE_COLORS)[index % ($glowColors ?? SPARKLE_COLORS).length],
|
|
186
|
+
left: position.left,
|
|
187
|
+
top: position.top,
|
|
188
|
+
delay: index * .12
|
|
189
|
+
}));
|
|
190
|
+
return /*#__PURE__*/ jsxs(StyledChatForm, {
|
|
191
|
+
className: "chat-composer",
|
|
192
|
+
onSubmit: (event) => {
|
|
193
|
+
event.preventDefault();
|
|
194
|
+
submit();
|
|
195
|
+
},
|
|
196
|
+
"data-chat-composer": true,
|
|
197
|
+
children: [
|
|
198
|
+
$glow && /*#__PURE__*/ jsx(GradientAngleProperty, {}),
|
|
199
|
+
/*#__PURE__*/ jsxs(StyledInputWrapper, {
|
|
200
|
+
$glow,
|
|
201
|
+
$glowColors,
|
|
202
|
+
$isActive: isActive,
|
|
203
|
+
onMouseEnter: () => setIsHovered(true),
|
|
204
|
+
onMouseLeave: () => setIsHovered(false),
|
|
205
|
+
children: [
|
|
206
|
+
$glow && /*#__PURE__*/ jsx(StyledRadiantGlow, {
|
|
207
|
+
$glowColors,
|
|
208
|
+
$isActive: isActive
|
|
209
|
+
}),
|
|
210
|
+
$glow && /*#__PURE__*/ jsx(StyledSparkleContainer, {
|
|
211
|
+
$isActive: isActive,
|
|
212
|
+
children: sparkles.map((sparkle, index) => /*#__PURE__*/ jsx(StyledSparkle, {
|
|
213
|
+
$color: sparkle.color,
|
|
214
|
+
$left: sparkle.left,
|
|
215
|
+
$top: sparkle.top,
|
|
216
|
+
$delay: sparkle.delay
|
|
217
|
+
}, index))
|
|
218
|
+
}),
|
|
219
|
+
/*#__PURE__*/ jsx(StyledTextarea, {
|
|
220
|
+
$glow,
|
|
221
|
+
rows: 1,
|
|
222
|
+
autoComplete: "off",
|
|
223
|
+
placeholder,
|
|
224
|
+
"aria-label": ariaLabel,
|
|
225
|
+
...props,
|
|
226
|
+
value: currentValue,
|
|
227
|
+
onChange: (event) => setValue(event.target.value),
|
|
228
|
+
onKeyDown: handleKeyDown,
|
|
229
|
+
onFocus: (event) => {
|
|
230
|
+
setIsFocused(true);
|
|
231
|
+
props.onFocus?.(event);
|
|
232
|
+
},
|
|
233
|
+
onBlur: (event) => {
|
|
234
|
+
setIsFocused(false);
|
|
235
|
+
props.onBlur?.(event);
|
|
236
|
+
},
|
|
237
|
+
ref: (node) => {
|
|
238
|
+
textareaRef.current = node;
|
|
239
|
+
chat.inputRef.current = node;
|
|
240
|
+
if (typeof ref === "function") ref(node);
|
|
241
|
+
else if (ref) ref.current = node;
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
]
|
|
245
|
+
}),
|
|
246
|
+
/*#__PURE__*/ jsx(StyledSendButton, {
|
|
247
|
+
type: "submit",
|
|
248
|
+
disabled: loading || !hasContent,
|
|
249
|
+
"aria-label": loading ? "Loading response" : "Submit question",
|
|
250
|
+
children: loading ? /*#__PURE__*/ jsx(Spinner, {}) : /*#__PURE__*/ jsx(Icon, { name: "ArrowUp" })
|
|
251
|
+
})
|
|
252
|
+
]
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
var ChatInput = /*#__PURE__*/ forwardRef(LocalChatInput);
|
|
256
|
+
//#endregion
|
|
257
|
+
export { ChatInput };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
export interface ChatLauncherProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
3
|
+
children?: React.ReactNode;
|
|
4
|
+
/** The animated rainbow halo. Off by default. */
|
|
5
|
+
$glow?: boolean;
|
|
6
|
+
/** Overrides the glow palette. */
|
|
7
|
+
$glowColors?: string[];
|
|
8
|
+
}
|
|
9
|
+
declare const ChatLauncher: React.ForwardRefExoticComponent<ChatLauncherProps & React.RefAttributes<HTMLButtonElement>>;
|
|
10
|
+
export { ChatLauncher };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
import { alpha, interactiveStyles } from "./utils/mixins.js";
|
|
4
|
+
import { Icon } from "./icon.js";
|
|
5
|
+
import { ChatContext } from "./chat-provider.js";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { forwardRef, useContext } from "react";
|
|
8
|
+
import styled, { createGlobalStyle, css, keyframes } from "styled-components";
|
|
9
|
+
//#region src/lib/chat-launcher.tsx
|
|
10
|
+
var GLOW_COLORS = [
|
|
11
|
+
"#cc5555",
|
|
12
|
+
"#d9a745",
|
|
13
|
+
"#3ab0cc",
|
|
14
|
+
"#cc7fc2",
|
|
15
|
+
"#4380cc",
|
|
16
|
+
"#4c1fa3"
|
|
17
|
+
];
|
|
18
|
+
var GradientAngleProperty = createGlobalStyle([`@property --cherry-gradient-angle{syntax:"<angle>";initial-value:0deg;inherits:false;}`]);
|
|
19
|
+
var rotateGradient = keyframes([`0%{--cherry-gradient-angle:0deg;}100%{--cherry-gradient-angle:360deg;}`]);
|
|
20
|
+
var pulseGlow = keyframes([`0%,100%{opacity:0.5;filter:blur(16px);}50%{opacity:1;filter:blur(22px);}`]);
|
|
21
|
+
var conicStops = (colors) => [...colors, colors[0]].join(", ");
|
|
22
|
+
var StyledChatLauncher = styled.button.withConfig({
|
|
23
|
+
displayName: "chat-launcher__StyledChatLauncher",
|
|
24
|
+
componentId: "sc-2dd3d5f9-0"
|
|
25
|
+
})([
|
|
26
|
+
``,
|
|
27
|
+
`;appearance:none;outline:none;cursor:pointer;background:`,
|
|
28
|
+
`;border:solid 1px `,
|
|
29
|
+
`;color:`,
|
|
30
|
+
`;border-radius:`,
|
|
31
|
+
`;padding:0;font-size:12px;font-family:inherit;font-weight:600;& .chat-launcher-inner{padding:6px 8px;display:flex;align-items:center;gap:6px;background:`,
|
|
32
|
+
`;border-radius:`,
|
|
33
|
+
`;}& svg{width:16px;height:16px;color:inherit;}`,
|
|
34
|
+
``
|
|
35
|
+
], interactiveStyles, ({ theme }) => theme.colors.light, ({ theme }) => theme.colors.grayLight, ({ theme }) => theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark, ({ theme }) => theme.spacing.radius.xs, ({ theme }) => theme.colors.light, ({ theme }) => theme.spacing.radius.xs, ({ $glow, $glowColors = GLOW_COLORS, $isActive, theme }) => $glow && css([
|
|
36
|
+
`position:relative;isolation:isolate;&::before,&::after,& .chat-launcher-ring{content:"";position:absolute;background:conic-gradient( from var(--cherry-gradient-angle),`,
|
|
37
|
+
` );opacity:0;transition:opacity 0.3s ease;animation:`,
|
|
38
|
+
` 3s linear infinite;z-index:-1;pointer-events:none;}&::before{inset:-1px;border-radius:calc(`,
|
|
39
|
+
` + 1px);}&::after{inset:-2px;border-radius:calc(`,
|
|
40
|
+
` + 2px);}& .chat-launcher-ring{inset:-4px;border-radius:calc(`,
|
|
41
|
+
` + 4px);}& .chat-launcher-glow{position:absolute;inset:-8px;border-radius:14px;pointer-events:none;z-index:-3;opacity:0.6;transition:all 0.3s ease;&::before{content:"";position:absolute;inset:0;border-radius:inherit;background:conic-gradient( from var(--cherry-gradient-angle),`,
|
|
42
|
+
` );animation:`,
|
|
43
|
+
` 3s linear infinite,`,
|
|
44
|
+
` 2s ease-in-out infinite;}}&:hover,&:focus,&:active{border-color:`,
|
|
45
|
+
`;box-shadow:none;}&:hover::before,&:focus::before,&:active::before{opacity:1;}&:active::after{opacity:1;}&:focus .chat-launcher-ring{opacity:1;}&:hover .chat-launcher-glow,&:focus .chat-launcher-glow,&:active .chat-launcher-glow{opacity:1;}`,
|
|
46
|
+
` @media (prefers-reduced-motion:reduce){&::before,&::after,& .chat-launcher-ring,& .chat-launcher-glow::before{animation:none;}}`
|
|
47
|
+
], conicStops($glowColors), rotateGradient, theme.spacing.radius.xs, theme.spacing.radius.xs, theme.spacing.radius.xs, conicStops($glowColors.map((color) => alpha(color, 40))), rotateGradient, pulseGlow, theme.colors.grayLight, $isActive && css([`&::before,& .chat-launcher-glow{opacity:1;}`])));
|
|
48
|
+
function LocalChatLauncher({ children, $glow = false, $glowColors, onClick, "aria-label": ariaLabel, ...props }, ref) {
|
|
49
|
+
const { toggle, isOpen } = useContext(ChatContext);
|
|
50
|
+
return /*#__PURE__*/ jsxs(Fragment, { children: [$glow && /*#__PURE__*/ jsx(GradientAngleProperty, {}), /*#__PURE__*/ jsxs(StyledChatLauncher, {
|
|
51
|
+
type: "button",
|
|
52
|
+
$glow,
|
|
53
|
+
$glowColors,
|
|
54
|
+
$isActive: isOpen,
|
|
55
|
+
"aria-label": ariaLabel ?? "Ask AI Assistant",
|
|
56
|
+
"aria-expanded": isOpen,
|
|
57
|
+
onClick: onClick ?? toggle,
|
|
58
|
+
...props,
|
|
59
|
+
ref,
|
|
60
|
+
children: [
|
|
61
|
+
$glow && /*#__PURE__*/ jsx("span", {
|
|
62
|
+
className: "chat-launcher-glow",
|
|
63
|
+
"aria-hidden": "true"
|
|
64
|
+
}),
|
|
65
|
+
$glow && /*#__PURE__*/ jsx("span", {
|
|
66
|
+
className: "chat-launcher-ring",
|
|
67
|
+
"aria-hidden": "true"
|
|
68
|
+
}),
|
|
69
|
+
/*#__PURE__*/ jsx("span", {
|
|
70
|
+
className: "chat-launcher-inner",
|
|
71
|
+
children: children ?? /*#__PURE__*/ jsxs(Fragment, { children: [/*#__PURE__*/ jsx(Icon, { name: "Sparkles" }), "Ask AI"] })
|
|
72
|
+
})
|
|
73
|
+
]
|
|
74
|
+
})] });
|
|
75
|
+
}
|
|
76
|
+
var ChatLauncher = /*#__PURE__*/ forwardRef(LocalChatLauncher);
|
|
77
|
+
//#endregion
|
|
78
|
+
export { ChatLauncher };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
export interface ChatMessageListProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
children: React.ReactNode;
|
|
4
|
+
/**
|
|
5
|
+
* While true, auto-scroll jumps instead of smooth-scrolling so a streaming
|
|
6
|
+
* reply does not fight the animation. Defaults to the provider's loading
|
|
7
|
+
* state when inside a ChatProvider.
|
|
8
|
+
*/
|
|
9
|
+
$streaming?: boolean;
|
|
10
|
+
}
|
|
11
|
+
declare const ChatMessageList: React.ForwardRefExoticComponent<ChatMessageListProps & React.RefAttributes<HTMLDivElement>>;
|
|
12
|
+
export { ChatMessageList };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
import { thinScrollbar } from "./utils/mixins.js";
|
|
4
|
+
import { ChatContext } from "./chat-provider.js";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
import { forwardRef, useContext, useEffect, useLayoutEffect, useRef } from "react";
|
|
7
|
+
import styled from "styled-components";
|
|
8
|
+
//#region src/lib/chat-message-list.tsx
|
|
9
|
+
var StyledChatMessageList = styled.div.withConfig({
|
|
10
|
+
displayName: "chat-message-list__StyledChatMessageList",
|
|
11
|
+
componentId: "sc-46a93f6c-0"
|
|
12
|
+
})([`display:flex;flex-direction:column;gap:20px;flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;padding:20px;-webkit-overflow-scrolling:touch;`, `;`], thinScrollbar);
|
|
13
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
14
|
+
function LocalChatMessageList({ children, $streaming, ...props }, ref) {
|
|
15
|
+
const { isOpen, loading } = useContext(ChatContext);
|
|
16
|
+
const streaming = $streaming ?? loading;
|
|
17
|
+
const scrollRef = useRef(null);
|
|
18
|
+
const scrollFrameRef = useRef(null);
|
|
19
|
+
const shouldAutoScrollRef = useRef(true);
|
|
20
|
+
const streamingRef = useRef(streaming);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
streamingRef.current = streaming;
|
|
23
|
+
}, [streaming]);
|
|
24
|
+
const jumpNextScrollRef = useRef(false);
|
|
25
|
+
useIsomorphicLayoutEffect(() => {
|
|
26
|
+
if (isOpen) {
|
|
27
|
+
shouldAutoScrollRef.current = true;
|
|
28
|
+
jumpNextScrollRef.current = true;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (scrollFrameRef.current !== null) {
|
|
32
|
+
cancelAnimationFrame(scrollFrameRef.current);
|
|
33
|
+
scrollFrameRef.current = null;
|
|
34
|
+
}
|
|
35
|
+
}, [isOpen]);
|
|
36
|
+
const prevLoadingRef = useRef(loading);
|
|
37
|
+
useIsomorphicLayoutEffect(() => {
|
|
38
|
+
if (loading && !prevLoadingRef.current) shouldAutoScrollRef.current = true;
|
|
39
|
+
prevLoadingRef.current = loading;
|
|
40
|
+
}, [loading]);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!shouldAutoScrollRef.current) return;
|
|
43
|
+
if (scrollFrameRef.current !== null) return;
|
|
44
|
+
scrollFrameRef.current = requestAnimationFrame(() => {
|
|
45
|
+
scrollFrameRef.current = null;
|
|
46
|
+
const scrollContainer = scrollRef.current;
|
|
47
|
+
if (!scrollContainer || !shouldAutoScrollRef.current) return;
|
|
48
|
+
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
49
|
+
const jump = jumpNextScrollRef.current;
|
|
50
|
+
jumpNextScrollRef.current = false;
|
|
51
|
+
scrollContainer.scrollTo({
|
|
52
|
+
top: scrollContainer.scrollHeight,
|
|
53
|
+
behavior: reduceMotion || streamingRef.current || jump ? "auto" : "smooth"
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}, [
|
|
57
|
+
children,
|
|
58
|
+
isOpen,
|
|
59
|
+
streaming
|
|
60
|
+
]);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
return () => {
|
|
63
|
+
if (scrollFrameRef.current !== null) cancelAnimationFrame(scrollFrameRef.current);
|
|
64
|
+
};
|
|
65
|
+
}, []);
|
|
66
|
+
const handleScroll = (event) => {
|
|
67
|
+
const scrollContainer = scrollRef.current;
|
|
68
|
+
if (!scrollContainer) return;
|
|
69
|
+
const distanceFromBottom = scrollContainer.scrollHeight - scrollContainer.scrollTop - scrollContainer.clientHeight;
|
|
70
|
+
shouldAutoScrollRef.current = distanceFromBottom <= scrollContainer.clientHeight * .15;
|
|
71
|
+
props.onScroll?.(event);
|
|
72
|
+
};
|
|
73
|
+
return /*#__PURE__*/ jsx(StyledChatMessageList, {
|
|
74
|
+
role: "log",
|
|
75
|
+
className: "chat-body",
|
|
76
|
+
...props,
|
|
77
|
+
onScroll: handleScroll,
|
|
78
|
+
ref: (node) => {
|
|
79
|
+
scrollRef.current = node;
|
|
80
|
+
if (typeof ref === "function") ref(node);
|
|
81
|
+
else if (ref) ref.current = node;
|
|
82
|
+
},
|
|
83
|
+
children
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
var ChatMessageList = /*#__PURE__*/ forwardRef(LocalChatMessageList);
|
|
87
|
+
//#endregion
|
|
88
|
+
export { ChatMessageList };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { Theme } from './utils';
|
|
3
|
+
export type ChatMessageRole = "user" | "assistant";
|
|
4
|
+
export interface ChatMessageProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
$role?: ChatMessageRole;
|
|
7
|
+
/** Rendered beside the message, typically an <Avatar>. */
|
|
8
|
+
$avatar?: React.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The chat type scale: one step tighter than body text so a transcript stays
|
|
12
|
+
* dense. Shared by the message bubbles and the typing indicator.
|
|
13
|
+
*/
|
|
14
|
+
export declare const chatTextStyles: (theme: Theme) => import('styled-components').RuleSet<object>;
|
|
15
|
+
declare const ChatMessage: React.ForwardRefExoticComponent<ChatMessageProps & React.RefAttributes<HTMLDivElement>>;
|
|
16
|
+
export { ChatMessage };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
import { mq } from "./utils/theme.js";
|
|
4
|
+
import { darkFilledTextRule, filledTextColor } from "./utils/mixins.js";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { forwardRef } from "react";
|
|
7
|
+
import styled, { css } from "styled-components";
|
|
8
|
+
//#region src/lib/chat-message.tsx
|
|
9
|
+
/**
|
|
10
|
+
* The chat type scale: one step tighter than body text so a transcript stays
|
|
11
|
+
* dense. Shared by the message bubbles and the typing indicator.
|
|
12
|
+
*/ var chatTextStyles = (theme) => css([
|
|
13
|
+
`font-size:`,
|
|
14
|
+
`;line-height:`,
|
|
15
|
+
`;`,
|
|
16
|
+
`{font-size:`,
|
|
17
|
+
`;line-height:`,
|
|
18
|
+
`;}`
|
|
19
|
+
], theme.fontSizes.text.xs, theme.lineHeights.text.xs, mq("lg"), theme.fontSizes.small.lg, theme.lineHeights.small.lg);
|
|
20
|
+
var StyledChatMessage = styled.div.withConfig({
|
|
21
|
+
displayName: "chat-message__StyledChatMessage",
|
|
22
|
+
componentId: "sc-878d3851-0"
|
|
23
|
+
})([
|
|
24
|
+
`display:flex;align-items:flex-end;gap:10px;width:100%;& .chat-message-avatar{flex-shrink:0;display:flex;align-items:center;height:calc( `,
|
|
25
|
+
` * `,
|
|
26
|
+
` );`,
|
|
27
|
+
`{height:calc( `,
|
|
28
|
+
` * `,
|
|
29
|
+
` );}}& .chat-message-content{min-width:0;`,
|
|
30
|
+
`;& p{`,
|
|
31
|
+
`;}& ul li,& ol li{`,
|
|
32
|
+
`;}}`,
|
|
33
|
+
``
|
|
34
|
+
], ({ theme }) => theme.fontSizes.text.xs, ({ theme }) => theme.lineHeights.text.xs, mq("lg"), ({ theme }) => theme.fontSizes.small.lg, ({ theme }) => theme.lineHeights.small.lg, ({ theme }) => chatTextStyles(theme), ({ theme }) => chatTextStyles(theme), ({ theme }) => chatTextStyles(theme), ({ $role, theme }) => $role === "user" ? css([
|
|
35
|
+
`flex-direction:row-reverse;& .chat-message-avatar{margin-bottom:9px;}& .chat-message-content{background:`,
|
|
36
|
+
`;color:`,
|
|
37
|
+
`;`,
|
|
38
|
+
`;padding:9px 14px;border-radius:16px 16px 4px 16px;margin-left:auto;width:fit-content;max-width:min(560px,88%);font-weight:500;white-space:pre-wrap;overflow-wrap:anywhere;}`
|
|
39
|
+
], theme.colors.primary, filledTextColor(theme), darkFilledTextRule(theme)) : css([`& .chat-message-content{color:`, `;width:100%;}& .chat-message-avatar{position:sticky;bottom:0;}`], theme.colors.dark));
|
|
40
|
+
function LocalChatMessage({ children, $role = "assistant", $avatar, ...props }, ref) {
|
|
41
|
+
return /*#__PURE__*/ jsxs(StyledChatMessage, {
|
|
42
|
+
$role,
|
|
43
|
+
...props,
|
|
44
|
+
ref,
|
|
45
|
+
children: [$avatar && /*#__PURE__*/ jsx("span", {
|
|
46
|
+
className: "chat-message-avatar",
|
|
47
|
+
children: $avatar
|
|
48
|
+
}), /*#__PURE__*/ jsx("div", {
|
|
49
|
+
className: "chat-message-content",
|
|
50
|
+
children
|
|
51
|
+
})]
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
var ChatMessage = /*#__PURE__*/ forwardRef(LocalChatMessage);
|
|
55
|
+
//#endregion
|
|
56
|
+
export { ChatMessage, chatTextStyles };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { IconProps } from './icon';
|
|
3
|
+
export type ChatPanelVariant = "drawer" | "inline" | "fullscreen";
|
|
4
|
+
export interface ChatPanelProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
/**
|
|
7
|
+
* drawer: fixed side panel, full-screen modal below the lg breakpoint.
|
|
8
|
+
* inline: a normal flow-sized container, always visible, no dialog
|
|
9
|
+
* semantics; the app controls its size and presence.
|
|
10
|
+
* fullscreen: fills the viewport, always modal.
|
|
11
|
+
*/
|
|
12
|
+
$variant?: ChatPanelVariant;
|
|
13
|
+
$side?: "left" | "right";
|
|
14
|
+
/** Drawer width from the lg breakpoint up. */
|
|
15
|
+
$width?: number;
|
|
16
|
+
/** Header title. Pass null to render no header text. */
|
|
17
|
+
$title?: React.ReactNode;
|
|
18
|
+
$titleIcon?: IconProps | null;
|
|
19
|
+
/** Extra header controls (reset button, …), placed before the close button. */
|
|
20
|
+
$actions?: React.ReactNode;
|
|
21
|
+
$hideCloseButton?: boolean;
|
|
22
|
+
"aria-label"?: string;
|
|
23
|
+
}
|
|
24
|
+
declare const ChatPanel: React.ForwardRefExoticComponent<ChatPanelProps & React.RefAttributes<HTMLDivElement>>;
|
|
25
|
+
export { ChatPanel };
|