替换公共自适应输入框实现

使用 Lexical 管理受控纯文本输入

接入 OverlayScrollbars 统一内部滚动条

补充编辑器状态与滚动结构行为测试
This commit is contained in:
2026-08-07 21:45:16 +08:00
parent 9dab1f53b9
commit 6ab546be1f
7 changed files with 764 additions and 393 deletions
+29
View File
@@ -30,6 +30,8 @@
"lexical": "^0.47.0",
"lucide-react": "^0.546.0",
"motion": "^12.23.24",
"overlayscrollbars": "2.16.0",
"overlayscrollbars-react": "0.5.6",
"qrcode": "^1.5.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
@@ -15895,6 +15897,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/overlayscrollbars": {
"version": "2.16.0",
"resolved": "https://registry.npmjs.org/overlayscrollbars/-/overlayscrollbars-2.16.0.tgz",
"integrity": "sha512-N03oje/q7j93D0aLZtoCdsDSYLmhheSsv8H7oSLE7HhdV9P/bmCURtLV/KbPye7P/bpfyt/obSfDpGUYoJ0OWg==",
"license": "MIT"
},
"node_modules/overlayscrollbars-react": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/overlayscrollbars-react/-/overlayscrollbars-react-0.5.6.tgz",
"integrity": "sha512-E5To04bL5brn9GVCZ36SnfGanxa2I2MDkWoa4Cjo5wol7l+diAgi4DBc983V7l2nOk/OLJ6Feg4kySspQEGDBw==",
"license": "MIT",
"peerDependencies": {
"overlayscrollbars": "^2.0.0",
"react": ">=16.8.0"
}
},
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -31502,6 +31520,17 @@
"wcwidth": "^1.0.1"
}
},
"overlayscrollbars": {
"version": "2.16.0",
"resolved": "https://registry.npmjs.org/overlayscrollbars/-/overlayscrollbars-2.16.0.tgz",
"integrity": "sha512-N03oje/q7j93D0aLZtoCdsDSYLmhheSsv8H7oSLE7HhdV9P/bmCURtLV/KbPye7P/bpfyt/obSfDpGUYoJ0OWg=="
},
"overlayscrollbars-react": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/overlayscrollbars-react/-/overlayscrollbars-react-0.5.6.tgz",
"integrity": "sha512-E5To04bL5brn9GVCZ36SnfGanxa2I2MDkWoa4Cjo5wol7l+diAgi4DBc983V7l2nOk/OLJ6Feg4kySspQEGDBw==",
"requires": {}
},
"p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+2
View File
@@ -191,6 +191,8 @@
"lexical": "^0.47.0",
"lucide-react": "^0.546.0",
"motion": "^12.23.24",
"overlayscrollbars": "2.16.0",
"overlayscrollbars-react": "0.5.6",
"qrcode": "^1.5.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
@@ -0,0 +1,47 @@
import { act } from '@testing-library/react';
import {
$createLineBreakNode,
$createParagraphNode,
$createTextNode,
$getRoot,
getNearestEditorFromDOMNode,
} from 'lexical';
export function setPlainTextEditorValue(
contentEditable: HTMLElement,
value: string,
) {
const editor = getNearestEditorFromDOMNode(contentEditable);
if (!editor) {
throw new Error('Expected a Lexical editor root');
}
act(() => {
editor.update(
() => {
const paragraph = $createParagraphNode();
value.split('\n').forEach((line, index, lines) => {
if (line) {
paragraph.append($createTextNode(line));
}
if (index < lines.length - 1) {
paragraph.append($createLineBreakNode());
}
});
$getRoot().clear().append(paragraph);
paragraph.selectEnd();
},
{ discrete: true },
);
});
}
export function getPlainTextEditorHost(contentEditable: HTMLElement) {
const host = contentEditable.closest<HTMLElement>(
'.auto-grow-text-area',
);
if (!host) {
throw new Error('Expected an auto-grow plain-text editor host');
}
return host;
}
+145 -258
View File
@@ -1,288 +1,175 @@
/* @vitest-environment jsdom */
import { act, fireEvent, render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { useState } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { AutoGrowTextArea } from './AutoGrowTextArea';
afterEach(() => {
vi.unstubAllGlobals();
beforeEach(() => {
Range.prototype.getBoundingClientRect = vi.fn(() => new DOMRect());
Range.prototype.getClientRects = vi.fn(() => [] as unknown as DOMRectList);
vi.stubGlobal(
'ResizeObserver',
class ResizeObserver {
observe() {}
unobserve() {}
disconnect() {}
},
);
vi.stubGlobal(
'ClipboardEvent',
class ClipboardEvent extends Event {
clipboardData: DataTransfer;
constructor(
type: string,
init: EventInit & { clipboardData: DataTransfer },
) {
super(type, init);
this.clipboardData = init.clipboardData;
}
},
);
});
function pastePlainText(editor: HTMLElement, value: string) {
const clipboardData = {
files: [],
getData: (type: string) => (type === 'text/plain' ? value : ''),
items: [],
types: ['text/plain'],
} as unknown as DataTransfer;
fireEvent(
editor,
new ClipboardEvent('paste', {
bubbles: true,
cancelable: true,
clipboardData,
}),
);
}
function ControlledEditor({
initialValue = '',
maxLength,
}: {
initialValue?: string;
maxLength?: number;
}) {
const [value, setValue] = useState(initialValue);
return (
<>
<AutoGrowTextArea
aria-label="受控编辑器"
value={value}
maxLength={maxLength}
onValueChange={setValue}
/>
<output aria-label="React 文本状态">{value}</output>
<button type="button" onClick={() => setValue('外部\n更新')}>
</button>
</>
);
}
describe('AutoGrowTextArea', () => {
it('uses native field sizing when the browser supports it', () => {
const supports = vi.fn().mockReturnValue(true);
const resizeObserver = vi.fn();
const onChange = vi.fn();
vi.stubGlobal('CSS', { supports });
vi.stubGlobal('ResizeObserver', resizeObserver);
it('keeps React state authoritative and preserves exact line breaks', async () => {
const user = userEvent.setup();
render(<ControlledEditor initialValue={'第一行\n第二行'} />);
render(
<AutoGrowTextArea
aria-label="自动增长输入框"
value=""
onChange={onChange}
/>,
const editor = screen.getByRole('textbox', { name: '受控编辑器' });
expect(screen.getByLabelText('React 文本状态').textContent).toBe(
'第一行\n第二行',
);
const input = screen.getByLabelText(
'自动增长输入框',
) as HTMLTextAreaElement;
expect(supports).toHaveBeenCalledWith('field-sizing', 'content');
expect(resizeObserver).not.toHaveBeenCalled();
expect(input.getAttribute('rows')).toBe('1');
expect(input.className).toContain('auto-grow-text-area');
expect(input.className).toContain('[field-sizing:content]');
expect(input.className).toContain('min-h-10');
expect(input.className).toContain('max-h-32');
expect(input.className).toContain('overflow-y-auto');
expect(input.className).toContain('disabled:cursor-not-allowed');
expect(input.className).toContain('disabled:opacity-60');
expect(input.style.height).toBe('');
fireEvent.change(input, { target: { value: '新内容' } });
expect(onChange).toHaveBeenCalledTimes(1);
await user.click(screen.getByRole('button', { name: '外部更新' }));
expect(screen.getByLabelText('React 文本状态').textContent).toBe(
'外部\n更新',
);
});
it('falls back to measured sizing, preserves css limits and remeasures width changes', () => {
let resizeObserverCallback!: ResizeObserverCallback;
const observe = vi.fn();
const disconnect = vi.fn();
vi.stubGlobal('CSS', { supports: vi.fn().mockReturnValue(false) });
vi.stubGlobal(
'ResizeObserver',
vi.fn().mockImplementation((callback: ResizeObserverCallback) => {
resizeObserverCallback = callback;
return {
observe,
unobserve: vi.fn(),
disconnect,
};
}),
it('publishes edits through onValueChange and enforces maxLength', async () => {
const user = userEvent.setup();
render(<ControlledEditor maxLength={4} />);
const editor = screen.getByRole('textbox', { name: '受控编辑器' });
await user.click(editor);
pastePlainText(editor, '12345');
await waitFor(() =>
expect(screen.getByLabelText('React 文本状态').textContent).toBe('1234'),
);
expect(editor.textContent).toBe('1234');
});
it('supports undo and redo while keeping React state synchronized', async () => {
const user = userEvent.setup();
render(<ControlledEditor />);
const editor = screen.getByRole('textbox', { name: '受控编辑器' });
await user.click(editor);
pastePlainText(editor, '草稿');
await waitFor(() =>
expect(screen.getByLabelText('React 文本状态').textContent).toBe('草稿'),
);
const { rerender, unmount } = render(
await user.keyboard('{Control>}z{/Control}');
expect(screen.getByLabelText('React 文本状态').textContent).toBe('');
await user.keyboard('{Control>}{Shift>}z{/Shift}{/Control}');
expect(screen.getByLabelText('React 文本状态').textContent).toBe('草稿');
});
it('exposes read-only and disabled accessibility without form controls', () => {
const { container } = render(
<AutoGrowTextArea
aria-label="自动增长输入框"
value=""
style={{
boxSizing: 'border-box',
minHeight: '80px',
maxHeight: '128px',
}}
onChange={vi.fn()}
aria-label="只读编辑器"
value="只读内容"
disabled
readOnly
onValueChange={vi.fn()}
/>,
);
const input = screen.getByLabelText(
'自动增长输入框',
) as HTMLTextAreaElement;
expect(observe).toHaveBeenCalledWith(input);
let contentHeight = 92;
Object.defineProperty(input, 'offsetHeight', {
configurable: true,
get: () => {
const styleHeight = Number.parseFloat(input.style.height);
return Number.isNaN(styleHeight) ? 82 : styleHeight;
},
});
Object.defineProperty(input, 'clientHeight', {
configurable: true,
get: () => input.offsetHeight - 2,
});
Object.defineProperty(input, 'scrollHeight', {
configurable: true,
get: () => contentHeight,
});
rerender(
const editor = screen.getByRole('textbox', { name: '只读编辑器' });
expect(editor.getAttribute('aria-disabled')).toBe('true');
expect(editor.getAttribute('aria-readonly')).toBe('true');
expect(editor.getAttribute('tabindex')).toBe('-1');
expect(container.querySelector('textarea')).toBeNull();
expect(container.querySelector('form')).toBeNull();
});
it('uses a distinct internal viewport for overflow content', async () => {
const { container } = render(
<AutoGrowTextArea
aria-label="自动增长输入框"
value="第一行\n第二行\n第三行\n第四行\n第五行"
style={{
boxSizing: 'border-box',
minHeight: '80px',
maxHeight: '128px',
}}
onChange={vi.fn()}
aria-label="滚动编辑器"
value={Array.from({ length: 20 }, (_, index) => `${index + 1}`).join(
'\n',
)}
onValueChange={vi.fn()}
/>,
);
expect(input.style.height).toBe('94px');
expect(input.style.overflowY).toBe('hidden');
contentHeight = 180;
act(() => {
resizeObserverCallback(
[
{
target: input,
contentRect: { width: 180 },
} as unknown as ResizeObserverEntry,
],
{} as ResizeObserver,
const viewport = await waitFor(() => {
const element = container.querySelector<HTMLElement>(
'[data-overlayscrollbars-viewport]',
);
expect(element).not.toBeNull();
return element!;
});
expect(input.style.height).toBe('128px');
expect(input.style.overflowY).toBe('auto');
contentHeight = 30;
rerender(
<AutoGrowTextArea
aria-label="自动增长输入框"
value="短内容"
style={{
boxSizing: 'border-box',
minHeight: '80px',
maxHeight: '128px',
}}
onChange={vi.fn()}
/>,
const content = container.querySelector<HTMLElement>(
'[data-overlayscrollbars-content]',
);
expect(input.style.height).toBe('80px');
expect(input.style.overflowY).toBe('hidden');
unmount();
expect(disconnect).toHaveBeenCalledTimes(1);
expect(viewport).not.toBe(content);
expect(
viewport.contains(
screen.getByRole('textbox', { name: '滚动编辑器' }),
),
).toBe(true);
});
it('remeasures uncontrolled input changes and forwards the consumer input handler', () => {
const onInput = vi.fn();
vi.stubGlobal('CSS', { supports: vi.fn().mockReturnValue(false) });
vi.stubGlobal(
'ResizeObserver',
vi.fn().mockImplementation(() => ({
observe: vi.fn(),
unobserve: vi.fn(),
disconnect: vi.fn(),
})),
);
render(
<AutoGrowTextArea
aria-label="非受控自动增长输入框"
defaultValue="初始内容"
style={{
boxSizing: 'border-box',
minHeight: '40px',
maxHeight: '128px',
}}
onInput={onInput}
/>,
);
const input = screen.getByLabelText(
'非受控自动增长输入框',
) as HTMLTextAreaElement;
let contentHeight = 90;
Object.defineProperty(input, 'offsetHeight', {
configurable: true,
get: () => {
const styleHeight = Number.parseFloat(input.style.height);
return Number.isNaN(styleHeight) ? 42 : styleHeight;
},
});
Object.defineProperty(input, 'clientHeight', {
configurable: true,
get: () => input.offsetHeight - 2,
});
Object.defineProperty(input, 'scrollHeight', {
configurable: true,
get: () => contentHeight,
});
fireEvent.input(input, {
target: { value: '第一行\n第二行\n第三行\n第四行' },
});
expect(input.style.height).toBe('92px');
expect(input.style.overflowY).toBe('hidden');
contentHeight = 180;
fireEvent.input(input, {
target: { value: '很多行内容'.repeat(30) },
});
expect(input.style.height).toBe('128px');
expect(input.style.overflowY).toBe('auto');
contentHeight = 20;
fireEvent.input(input, { target: { value: '短内容' } });
expect(input.style.height).toBe('40px');
expect(input.style.overflowY).toBe('hidden');
expect(onInput).toHaveBeenCalledTimes(3);
});
it('measures content-box height without double-counting padding or borders', () => {
vi.stubGlobal('CSS', { supports: vi.fn().mockReturnValue(false) });
vi.stubGlobal(
'ResizeObserver',
vi.fn().mockImplementation(() => ({
observe: vi.fn(),
unobserve: vi.fn(),
disconnect: vi.fn(),
})),
);
const { rerender } = render(
<AutoGrowTextArea
aria-label="内容盒输入框"
value=""
style={{
boxSizing: 'content-box',
minHeight: '40px',
maxHeight: '128px',
paddingTop: '10px',
paddingBottom: '6px',
}}
onChange={vi.fn()}
/>,
);
const input = screen.getByLabelText(
'内容盒输入框',
) as HTMLTextAreaElement;
let contentHeight = 120;
Object.defineProperty(input, 'offsetHeight', {
configurable: true,
get: () => 122,
});
Object.defineProperty(input, 'clientHeight', {
configurable: true,
get: () => 120,
});
Object.defineProperty(input, 'scrollHeight', {
configurable: true,
get: () => contentHeight,
});
rerender(
<AutoGrowTextArea
aria-label="内容盒输入框"
value="多行内容"
style={{
boxSizing: 'content-box',
minHeight: '40px',
maxHeight: '128px',
paddingTop: '10px',
paddingBottom: '6px',
}}
onChange={vi.fn()}
/>,
);
expect(input.style.height).toBe('104px');
expect(input.style.overflowY).toBe('hidden');
contentHeight = 160;
rerender(
<AutoGrowTextArea
aria-label="内容盒输入框"
value="更多多行内容"
style={{
boxSizing: 'content-box',
minHeight: '40px',
maxHeight: '128px',
paddingTop: '10px',
paddingBottom: '6px',
}}
onChange={vi.fn()}
/>,
);
expect(input.style.height).toBe('128px');
expect(input.style.overflowY).toBe('auto');
});
});
File diff suppressed because it is too large Load Diff
+132 -39
View File
@@ -6783,13 +6783,13 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
}
.image-canvas-editor__generation-prompt {
--auto-grow-editor-radius: 1.05rem;
--auto-grow-editor-min-height: 3.35rem;
--auto-grow-editor-padding: 0.52rem 0.82rem;
grid-column: 1 / -1;
min-height: 3.35rem;
resize: none;
border: 1px solid rgba(15, 23, 42, 0.08);
border-radius: 1.05rem;
border-radius: var(--auto-grow-editor-radius);
background: #f8fafc;
padding: 0.52rem 0.82rem;
color: #0f172a;
font: inherit;
font-size: 0.82rem;
@@ -6798,17 +6798,13 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
outline: none;
}
.image-canvas-editor__generation-prompt:focus,
.image-canvas-editor__generation-prompt:focus-within,
.image-canvas-editor__video-prompt:focus {
border-color: var(--image-canvas-brand-border-strong);
background: #ffffff;
box-shadow: 0 0 0 3px var(--image-canvas-brand-focus-ring);
}
.image-canvas-editor__generation-prompt::placeholder {
color: #94a3b8;
}
.image-canvas-editor__video-prompt {
grid-column: 1 / -1;
min-height: 4.1rem;
@@ -7604,8 +7600,8 @@ button.image-canvas-editor__reference-chip:disabled {
.image-canvas-editor__character-composer
.image-canvas-editor__generation-prompt {
min-height: 5.1rem;
padding-top: 0.42rem;
--auto-grow-editor-min-height: 5.1rem;
--auto-grow-editor-padding: 0.42rem 0.82rem 0.52rem;
}
.image-canvas-editor__character-composer
@@ -7653,7 +7649,7 @@ button.image-canvas-editor__reference-chip:disabled {
}
.image-canvas-editor__publication-description {
min-height: 4rem;
--auto-grow-editor-min-height: 4rem;
}
.image-canvas-editor__publication-composer
@@ -7690,8 +7686,8 @@ button.image-canvas-editor__reference-chip:disabled {
}
.image-canvas-editor__icon-composer .image-canvas-editor__generation-prompt {
min-height: 5.1rem;
padding-top: 0.42rem;
--auto-grow-editor-min-height: 5.1rem;
--auto-grow-editor-padding: 0.42rem 0.82rem 0.52rem;
}
.image-canvas-editor__character-composer
@@ -7964,11 +7960,7 @@ button.image-canvas-editor__reference-chip:disabled {
}
.image-canvas-editor__spec-textarea {
min-height: 3.8rem;
}
.image-canvas-editor__spec-textarea::placeholder {
color: #94a3b8;
--auto-grow-editor-min-height: 3.8rem;
}
.image-canvas-editor__spec-textarea--view {
@@ -8549,7 +8541,7 @@ button.image-canvas-editor__reference-chip:disabled {
}
.image-canvas-editor__quick-edit-prompt {
min-height: 5.6rem;
--auto-grow-editor-min-height: 5.6rem;
}
.image-canvas-editor__quick-edit-controls {
@@ -8699,7 +8691,8 @@ button.image-canvas-editor__reference-chip:disabled {
.image-canvas-editor__character-animation-textarea {
grid-column: 1 / -1;
min-height: 8rem;
--auto-grow-editor-min-height: 8rem;
--auto-grow-editor-max-height: 16rem;
}
.image-canvas-editor__character-animation-presets {
@@ -8791,12 +8784,12 @@ button.image-canvas-editor__reference-chip:disabled {
}
.image-canvas-editor__generate-prompt {
min-height: 7rem;
resize: none;
--auto-grow-editor-radius: 0.45rem;
--auto-grow-editor-min-height: 7rem;
--auto-grow-editor-padding: 0.7rem;
border: 1px solid #d9dee8;
border-radius: 0.45rem;
border-radius: var(--auto-grow-editor-radius);
background: #f8fafc;
padding: 0.7rem;
color: #1f2937;
font: inherit;
font-size: 0.82rem;
@@ -8804,7 +8797,7 @@ button.image-canvas-editor__reference-chip:disabled {
outline: none;
}
.image-canvas-editor__generate-prompt:focus {
.image-canvas-editor__generate-prompt:focus-within {
border-color: var(--image-canvas-brand-border-strong);
background: #ffffff;
box-shadow: 0 0 0 3px var(--image-canvas-brand-focus-ring);
@@ -16796,28 +16789,128 @@ button {
}
.auto-grow-text-area {
scrollbar-color: #cbd5e1 transparent;
scrollbar-width: thin;
position: relative;
display: block;
box-sizing: border-box;
width: 100%;
min-width: 0;
min-height: var(--auto-grow-editor-min-height, 2.5rem);
max-height: var(--auto-grow-editor-max-height, 8rem);
border-radius: var(--auto-grow-editor-radius, 1rem);
overflow: hidden;
}
.auto-grow-text-area::-webkit-scrollbar {
width: 6px;
.auto-grow-text-area__content {
box-sizing: border-box;
width: 100%;
min-height: calc(var(--auto-grow-editor-min-height, 2.5rem) - 2px);
padding: var(--auto-grow-editor-padding, 0.5rem 0.75rem);
padding-right: 2.25rem;
outline: none;
overflow-wrap: anywhere;
white-space: pre-wrap;
-webkit-user-select: text;
user-select: text;
}
.auto-grow-text-area::-webkit-scrollbar-track {
margin-block: 10px;
background-color: transparent;
.auto-grow-text-area__placeholder {
position: absolute;
inset: 0;
box-sizing: border-box;
padding: var(--auto-grow-editor-padding, 0.5rem 0.75rem);
padding-right: 2.25rem;
overflow: hidden;
color: #94a3b8;
pointer-events: none;
text-overflow: ellipsis;
white-space: nowrap;
}
.auto-grow-text-area::-webkit-scrollbar-thumb {
border: 1px solid transparent;
.auto-grow-text-area__viewport,
.auto-grow-text-area [data-overlayscrollbars-viewport] {
/* 独立 viewport 不随内容增高,达到上限后只由它承接滚动。 */
display: block;
height: 100%;
min-height: 0;
overflow-x: hidden !important;
overflow-y: auto !important;
overscroll-behavior: contain;
}
.auto-grow-text-area__content-root {
box-sizing: border-box;
min-height: calc(var(--auto-grow-editor-min-height, 2.5rem) - 2px);
}
.auto-grow-text-area[data-disabled='true'] {
cursor: not-allowed;
opacity: 0.6;
}
.auto-grow-text-area[data-readonly='true'] {
cursor: default;
}
.editor-agent-conversation__draft-input {
--auto-grow-editor-radius: 1.5rem;
--auto-grow-editor-padding: 0.5rem 0.75rem;
border-radius: var(--auto-grow-editor-radius);
}
/* 微信式编辑器滚动条:轨道透明,8px 胶囊滑块内缩于输入框圆角之内。 */
.auto-grow-text-area
> .os-theme-auto-grow-text-area.os-scrollbar-vertical.os-scrollbar-cornerless {
--os-size: 8px;
--os-padding-axis: 0;
--os-padding-perpendicular: 0;
--os-track-border-radius: 999px;
--os-track-bg: transparent;
--os-track-bg-hover: transparent;
--os-track-bg-active: transparent;
--os-handle-border-radius: 999px;
--os-handle-bg: rgba(98, 98, 98, 0.56);
--os-handle-bg-hover: rgba(98, 98, 98, 0.72);
--os-handle-bg-active: rgba(98, 98, 98, 0.86);
--os-handle-min-size: 24px;
--os-handle-perpendicular-size: 100%;
--os-handle-perpendicular-size-hover: 100%;
--os-handle-perpendicular-size-active: 100%;
box-sizing: border-box;
top: var(--auto-grow-editor-radius, 1rem) !important;
right: 5px;
bottom: var(--auto-grow-editor-radius, 1rem) !important;
width: 8px;
padding: 0;
}
.auto-grow-text-area
> .os-theme-auto-grow-text-area.os-scrollbar-vertical
.os-scrollbar-track {
width: 8px;
border-radius: 999px;
background-color: #cbd5e1;
background-clip: padding-box;
background: transparent;
}
.auto-grow-text-area::-webkit-scrollbar-thumb:hover {
background-color: #94a3b8;
.auto-grow-text-area
> .os-theme-auto-grow-text-area.os-scrollbar-vertical
.os-scrollbar-handle {
width: 8px;
min-height: 24px;
border: 0;
border-radius: 999px;
background: rgba(98, 98, 98, 0.56);
}
.auto-grow-text-area
> .os-theme-auto-grow-text-area.os-scrollbar-vertical:hover
.os-scrollbar-handle {
background: rgba(98, 98, 98, 0.72);
}
.auto-grow-text-area
> .os-theme-auto-grow-text-area.os-scrollbar-vertical:active
.os-scrollbar-handle {
background: rgba(98, 98, 98, 0.86);
}
.image-canvas-editor__metadata-dialog.platform-modal-shell {
+1
View File
@@ -1,5 +1,6 @@
/* eslint-disable react-refresh/only-export-components */
import 'overlayscrollbars/styles/overlayscrollbars.css';
import './index.css';
import {StrictMode, Suspense} from 'react';