Skip to content

Commit

Permalink
✨ feat: Add code highlighting component and update styling in ChatInp…
Browse files Browse the repository at this point in the history
…utArea

- Import hooks and components
- Create new component for highlighting code snippets
- Modify styling and functionality in various components
- Remove theme prop and update styling in multiple components

These changes add a new code highlighting component and improve the styling and functionality in the ChatInputArea directory. The theme prop is removed and styling is updated in multiple components.
  • Loading branch information
canisminor1990 committed Jul 19, 2023
1 parent d5bb2d4 commit a9a316f
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 74 deletions.
39 changes: 39 additions & 0 deletions src/ChatInputArea/InputHighlight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useScroll, useSize } from 'ahooks';
import { memo, useEffect, useRef } from 'react';

import { SyntaxHighlighter } from '@/Highlighter';

import { useStyles } from './style';

interface InputHighlightProps {
target: string;
value: string;
}

const InputHighlight = memo<InputHighlightProps>(({ value, target }) => {
const ref: any = useRef(null);

const { styles } = useStyles();

const nativeTextarea = document.querySelector(`#${target}`);

const size = useSize(nativeTextarea);
const scroll = useScroll(nativeTextarea);

useEffect(() => {
ref.current.scroll(0, scroll?.top || 0);
}, [scroll?.top]);

return (
<div
className={styles.highlight}
data-code-type="highlighter"
ref={ref}
style={{ height: size?.height, width: size?.width }}
>
<SyntaxHighlighter language="markdown">{value.trim()}</SyntaxHighlighter>
</div>
);
});

export default InputHighlight;
73 changes: 39 additions & 34 deletions src/ChatInputArea/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import ActionIcon from '@/ActionIcon';
import { TextArea, type TextAreaProps } from '@/Input';

import InputHighlight from './InputHighlight';
import { useStyles } from './style';

export interface ChatInputAreaProps extends TextAreaProps {
Expand Down Expand Up @@ -139,40 +140,44 @@ const ChatInputArea = forwardRef<InputRef, ChatInputAreaProps>(
<ActionIcon icon={expand ? Minimize2 : Maximize2} onClick={handleExpandClick} />
</div>
</div>
<TextArea
className={cx(styles.textarea, textareaClassName)}
defaultValue={defaultValue}
ref={ref}
style={textareaStyle}
{...props}
onBlur={(e) => {
if (onBlur) onBlur(e);
setValue(e.target.value);
}}
onChange={(e) => {
if (onChange) onChange(e);
setValue(e.target.value);
}}
onCompositionEnd={(e) => {
if (onCompositionEnd) onCompositionEnd(e);
isChineseInput.current = false;
}}
onCompositionStart={(e) => {
if (onCompositionStart) onCompositionStart(e);
isChineseInput.current = true;
}}
onPressEnter={(e) => {
if (onPressEnter) onPressEnter(e);
if (!loading && !e.shiftKey && !isChineseInput.current) {
e.preventDefault();
handleSend();
}
}}
placeholder={placeholder}
resize={false}
type="pure"
value={value}
/>
<div className={styles.textareaContainer}>
<InputHighlight target={'lobe-chat-input-area'} value={value} />
<TextArea
className={cx(styles.textarea, textareaClassName)}
defaultValue={defaultValue}
id={'lobe-chat-input-area'}
ref={ref}
style={textareaStyle}
{...props}
onBlur={(e) => {
if (onBlur) onBlur(e);
setValue(e.target.value);
}}
onChange={(e) => {
if (onChange) onChange(e);
setValue(e.target.value);
}}
onCompositionEnd={(e) => {
if (onCompositionEnd) onCompositionEnd(e);
isChineseInput.current = false;
}}
onCompositionStart={(e) => {
if (onCompositionStart) onCompositionStart(e);
isChineseInput.current = true;
}}
onPressEnter={(e) => {
if (onPressEnter) onPressEnter(e);
if (!loading && !e.shiftKey && !isChineseInput.current) {
e.preventDefault();
handleSend();
}
}}
placeholder={placeholder}
resize={false}
type="pure"
value={value}
/>
</div>
<div className={styles.footerBar}>
{footer}
<Button disabled={disabled} loading={loading} onClick={handleSend} type="primary">
Expand Down
37 changes: 35 additions & 2 deletions src/ChatInputArea/style.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createStyles } from 'antd-style';

export const useStyles = createStyles(({ css }) => {
export const useStyles = createStyles(({ css, token }) => {
return {
actionLeft: css`
display: flex;
Expand Down Expand Up @@ -43,9 +43,42 @@ export const useStyles = createStyles(({ css }) => {
padding: 0 24px;
`,
highlight: css`
pointer-events: none;
position: absolute;
inset: 0;
overflow-x: hidden;
overflow-y: auto;
padding: 0 24px;
.shiki {
margin: 0;
}
pre {
font-family: ${token.fontFamilyCode} !important;
line-height: 1.5;
color: ${token.colorSuccess};
word-wrap: break-word;
white-space: pre-wrap;
}
`,
textarea: css`
flex: 1;
height: 100% !important;
padding: 0 24px;
font-family: ${token.fontFamilyCode} !important;
line-height: 1.5;
color: transparent;
caret-color: ${token.colorText};
`,
textareaContainer: css`
position: relative;
flex: 1;
`,
};
});
9 changes: 2 additions & 7 deletions src/CodeEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export interface CodeEditorProps {
tabSize?: number;
textareaClassName?: string;
textareaId?: string;
theme?: SyntaxHighlighterProps['theme'];
type?: 'ghost' | 'block' | 'pure';
value: string;
}
Expand All @@ -48,7 +47,7 @@ const CodeEditor = forwardRef<TextAreaRef, CodeEditorProps>(
{
style,
language,
theme,

value,
onValueChange,
resize = true,
Expand All @@ -65,11 +64,7 @@ const CodeEditor = forwardRef<TextAreaRef, CodeEditorProps>(
{/* @ts-ignore */}
<Editor
className={styles.editor}
highlight={(code) => (
<SyntaxHighlighter language={language} theme={theme}>
{code}
</SyntaxHighlighter>
)}
highlight={(code) => <SyntaxHighlighter language={language}>{code}</SyntaxHighlighter>}
onValueChange={onValueChange}
padding={0}
ref={ref as any}
Expand Down
24 changes: 15 additions & 9 deletions src/CodeEditor/style.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { createStyles } from 'antd-style';

export const useStyles = createStyles(
(
{ cx, css, token, isDarkMode },
{ type, resize }: { resize: boolean; type: 'ghost' | 'block' | 'pure' },
) => {
({ cx, css, token }, { type, resize }: { resize: boolean; type: 'ghost' | 'block' | 'pure' }) => {
const typeStylish = css`
padding: 8px 12px;
Expand All @@ -29,15 +26,22 @@ export const useStyles = createStyles(
css`
overflow-x: hidden;
overflow-y: auto;
width: 100%;
height: fit-content;
`,
),
editor: css`
resize: ${resize ? 'vertical' : 'none'};
height: fit-content;
min-height: 100%;
font-family: ${token.fontFamilyCode} !important;
textarea {
min-height: 36px !important;
}
pre {
min-height: 36px !important;
word-wrap: break-word;
white-space: pre-wrap;
Expand All @@ -49,17 +53,19 @@ export const useStyles = createStyles(
textarea: css`
overflow-x: hidden;
overflow-y: auto;
height: 100% !important;
color: transparent !important;
caret-color: ${token.colorText};
-webkit-text-fill-color: unset !important;
&::placeholder {
color: ${token.colorTextQuaternary};
}
&::selection {
color: #000;
background: ${isDarkMode ? token.yellow3A : token.yellow6A};
}
&:focus {
border: none !important;
outline: none !important;
Expand Down
6 changes: 6 additions & 0 deletions src/EditableMessage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export interface EditableMessageProps {
* @default false
*/
openModal?: boolean;
placeholder?: string;
/**
* @title Whether to show the edit button when the text value is empty
* @default false
Expand All @@ -64,6 +65,7 @@ const EditableMessage = memo<EditableMessageProps>(
editing,
openModal,
onOpenChange,
placeholder = 'Type something...',
showEditWhenEmpty = false,
}) => {
const [isEdit, setTyping] = useControlledState(false, {
Expand All @@ -79,10 +81,13 @@ const EditableMessage = memo<EditableMessageProps>(
return !value && showEditWhenEmpty ? (
<MessageInput
className={classNames.input}
defaultValue={value}
onCancel={() => setTyping(false)}
onConfirm={(text) => {
onChange?.(text);
setTyping(false);
}}
placeholder={placeholder}
/>
) : (
<>
Expand All @@ -105,6 +110,7 @@ const EditableMessage = memo<EditableMessageProps>(
onChange?.(text);
setTyping(false);
}}
placeholder={placeholder}
/>
) : (
<Markdown className={classNames.markdown}>{value}</Markdown>
Expand Down
7 changes: 1 addition & 6 deletions src/Highlighter/SyntaxHighlighter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export interface SyntaxHighlighterProps {
children: string;
language: string;
options?: HighlighterOptions;
theme?: 'dark' | 'light';
}

const SyntaxHighlighter = memo<SyntaxHighlighterProps>(({ children, language, options }) => {
Expand All @@ -29,11 +28,7 @@ const SyntaxHighlighter = memo<SyntaxHighlighterProps>(({ children, language, op
return (
<>
{isLoading ? (
<div className={styles.shiki}>
<pre>
<code>{children}</code>
</pre>
</div>
<code>{children}</code>
) : (
<div
className={styles.shiki}
Expand Down
5 changes: 5 additions & 0 deletions src/Highlighter/SyntaxHighlighter/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,13 @@ export const useStyles = createStyles(({ css, token, cx, prefixCls, stylish }) =
shiki: cx(
`${prefix}-shiki`,
css`
margin: 0;
padding: 0;
.shiki {
overflow-x: auto;
margin: 0;
padding: 0;
background: none !important;
}
`,
Expand Down
10 changes: 1 addition & 9 deletions src/Highlighter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ export interface HighlighterProps extends DivProps {
* @default false
*/
spotlight?: boolean;
/**
* @description The theme of the code block
* @default 'light'
*/
theme?: 'dark' | 'light';
/**
* @description The type of the code block
* @default 'block'
Expand All @@ -49,7 +44,6 @@ export const Highlighter = memo<HighlighterProps>(
language,
className,
style,
theme,
copyable = true,
showLanguage = true,
type = 'block',
Expand All @@ -64,9 +58,7 @@ export const Highlighter = memo<HighlighterProps>(
{spotlight && <Spotlight size={240} />}
{copyable && <CopyButton className={styles.button} content={children} placement="left" />}
{showLanguage && language && <div className={styles.lang}>{language.toLowerCase()}</div>}
<SyntaxHighlighter language={language?.toLowerCase()} theme={theme}>
{children.trim()}
</SyntaxHighlighter>
<SyntaxHighlighter language={language?.toLowerCase()}>{children.trim()}</SyntaxHighlighter>
</div>
);
},
Expand Down
Loading

1 comment on commit a9a316f

@vercel
Copy link

@vercel vercel bot commented on a9a316f Jul 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

lobe-ui – ./

lobe-ui.vercel.app
lobe-ui-git-master-lobehub.vercel.app
ui.lobehub.com
lobe-ui-lobehub.vercel.app

Please sign in to comment.