feat: refine sidebar panel layouts (#7285)

This commit is contained in:
白熱
2026-07-17 21:01:24 +08:00
committed by GitHub
parent bd69e47550
commit 5210f2af71
3 changed files with 186 additions and 23 deletions
@@ -77,8 +77,16 @@ const averageInfo: IFunctionInfo = {
],
};
const piInfo: IFunctionInfo = {
functionName: 'PI',
functionType: FunctionType.Math,
description: 'Returns pi.',
abstract: 'Pi.',
functionParameter: [],
};
class TestDescriptionService {
private readonly _items = [sumInfo, averageInfo];
private readonly _items = [sumInfo, averageInfo, piInfo];
getDescriptions() {
return new Map(this._items.map((item) => [item.functionName, item]));
@@ -393,6 +401,59 @@ describe('formula function picker views', () => {
container.remove();
});
it('renders selected function details as structured sections', async () => {
const { injector } = createFormulaViewTestBed();
await act(async () => {
root.render(
<RediContext.Provider value={{ injector }}>
<SelectFunction onChange={(value) => SelectionState.values.push(value)} />
</RediContext.Provider>
);
await Promise.resolve();
});
const details = container.querySelector('[data-u-comp="formula-function-details"]');
expect(details).not.toBeNull();
expect(details?.querySelector('[data-u-comp="formula-function-syntax"]')?.textContent).toBe('SUM(number1,...)');
expect(details?.querySelector('[data-u-comp="formula-function-example"]')?.textContent).toBe('SUM(A1)');
const parameters = details?.querySelectorAll('[data-u-comp="formula-function-parameter"]');
expect(parameters).toHaveLength(1);
expect(parameters?.[0].textContent).toContain('number1Required.First value.');
});
it('omits the parameter list when the selected function has no parameters', async () => {
const { injector } = createFormulaViewTestBed();
await act(async () => {
root.render(
<RediContext.Provider value={{ injector }}>
<SelectFunction onChange={(value) => SelectionState.values.push(value)} />
</RediContext.Provider>
);
await Promise.resolve();
});
const input = container.querySelector('input') as HTMLInputElement;
await act(async () => {
writeInput(input, 'PI');
await Promise.resolve();
});
const item = Array.from(container.querySelectorAll('li'))
.find((node) => node.textContent === 'PI') as HTMLElement | undefined;
expect(item).toBeDefined();
await act(async () => {
item!.click();
await Promise.resolve();
});
const details = container.querySelector('[data-u-comp="formula-function-details"]');
expect(details?.children).toHaveLength(2);
});
it('filters functions by typed text and reports the selected function info', async () => {
const { injector } = createFormulaViewTestBed();
@@ -20,14 +20,13 @@ import type { ISidebarMethodOptions } from '@univerjs/ui';
import type { KeyboardEvent } from 'react';
import type { LocaleKey } from '../../../locale/types';
import { IConfigService, LocaleService } from '@univerjs/core';
import { borderClassName, clsx, Input, scrollbarClassName, Select } from '@univerjs/design';
import { borderClassName, clsx, divideYClassName, Input, scrollbarClassName, Select } from '@univerjs/design';
import { CheckMarkIcon } from '@univerjs/icons';
import { IDescriptionService, PLUGIN_CONFIG_KEY_BASE } from '@univerjs/sheets-formula';
import { ISidebarService, useDependency, useObservable } from '@univerjs/ui';
import { useEffect, useState } from 'react';
import { getFunctionTypeValues } from '../../../services/utils';
import { FunctionHelp } from '../function-help/FunctionHelp';
import { FunctionParams } from '../function-params/FunctionParams';
export interface ISelectFunctionProps {
onChange: (functionInfo: IFunctionInfo | null) => void;
@@ -209,29 +208,131 @@ export function SelectFunction(props: ISelectFunctionProps) {
)}
{functionInfo && (
<div className={clsx('univer-mx-0 univer-my-2 univer-overflow-y-auto', scrollbarClassName)}>
<FunctionParams title={functionInfo.functionName} value={functionInfo.description} />
<div
data-u-comp="formula-function-details"
className={clsx('univer-mx-0 univer-my-3 univer-overflow-y-auto', scrollbarClassName)}
>
<div
className="
univer-rounded-lg univer-bg-gray-50 univer-p-3
dark:!univer-bg-gray-800
"
>
<div
className="
univer-text-sm univer-font-semibold univer-text-gray-900
dark:!univer-text-white
"
>
{functionInfo.functionName}
</div>
<div
className="
univer-mt-1 univer-text-xs univer-leading-5 univer-text-gray-600
dark:!univer-text-gray-300
"
>
{functionInfo.description}
</div>
</div>
<FunctionParams
title={localeService.t<LocaleKey>('sheets-formula-ui.moreFunctions.syntax')}
value={<FunctionHelp prefix={functionInfo.functionName} value={functionInfo.functionParameter} />}
/>
<div className="univer-mt-4 univer-flex univer-flex-col univer-gap-3">
<div>
<div
className="
univer-text-xs univer-font-medium univer-text-gray-500
dark:!univer-text-gray-300
"
>
{localeService.t<LocaleKey>('sheets-formula-ui.moreFunctions.syntax')}
</div>
<div
data-u-comp="formula-function-syntax"
className={clsx(`
univer-mt-1.5 univer-break-words univer-rounded-md univer-bg-gray-50 univer-px-3
univer-py-2 univer-font-mono univer-text-xs univer-leading-5 univer-text-gray-900
dark:!univer-bg-gray-800 dark:!univer-text-white
`, borderClassName)}
>
<FunctionHelp
prefix={functionInfo.functionName}
value={functionInfo.functionParameter}
/>
</div>
</div>
<FunctionParams
title={localeService.t<LocaleKey>('sheets-formula-ui.prompt.helpExample')}
value={`${functionInfo.functionName}(${functionInfo.functionParameter
.map((item) => item.example)
.join(',')})`}
/>
<div>
<div
className="
univer-text-xs univer-font-medium univer-text-gray-500
dark:!univer-text-gray-300
"
>
{localeService.t<LocaleKey>('sheets-formula-ui.prompt.helpExample')}
</div>
<div
data-u-comp="formula-function-example"
className={clsx(`
univer-mt-1.5 univer-break-words univer-rounded-md univer-bg-gray-50 univer-px-3
univer-py-2 univer-font-mono univer-text-xs univer-leading-5 univer-text-gray-900
dark:!univer-bg-gray-800 dark:!univer-text-white
`, borderClassName)}
>
{`${functionInfo.functionName}(${functionInfo.functionParameter
.map((item) => item.example)
.join(',')})`}
</div>
</div>
</div>
{functionInfo.functionParameter &&
functionInfo.functionParameter.map((item: IFunctionParam) => (
<FunctionParams
key={item.name}
title={item.name}
value={`${item.require ? required : optional} ${item.detail}`}
/>
))}
{functionInfo.functionParameter.length > 0 && (
<div
className={clsx('univer-mt-4 univer-rounded-lg univer-px-3', borderClassName, divideYClassName)}
>
{functionInfo.functionParameter.map((item: IFunctionParam) => (
<div
key={item.name}
data-u-comp="formula-function-parameter"
className="univer-py-3"
>
<div className="univer-flex univer-items-center univer-gap-2">
<span
className={`
univer-text-xs univer-font-medium univer-text-gray-900
dark:!univer-text-white
`}
>
{item.name}
</span>
<span
className={clsx(`
univer-rounded-full univer-px-2 univer-py-0.5 univer-text-xs
univer-leading-4
`, item.require
? `
univer-bg-primary-50 univer-text-primary-600
dark:!univer-bg-primary-900 dark:!univer-text-primary-200
`
: `
univer-bg-gray-100 univer-text-gray-600
dark:!univer-bg-gray-700 dark:!univer-text-gray-200
`)}
>
{item.require ? required : optional}
</span>
</div>
<div
className={`
univer-mt-1 univer-text-xs univer-leading-5 univer-text-gray-600
dark:!univer-text-gray-300
`}
>
{item.detail}
</div>
</div>
))}
</div>
)}
</div>
)}
</div>
@@ -163,6 +163,7 @@ export const ThreadCommentPanel = (props: IThreadCommentPanelProps) => {
const renderComment = (section: ThreadCommentPanelSection) => (comment: IThreadComment, index: number) => (
<ThreadCommentTree
full
location={location}
getSubUnitName={getSubUnitName}
key={getThreadCommentPanelItemKey(comment, index, section)}