Skip to content

Commit

Permalink
feat(design): add Textarea component (#1778)
Browse files Browse the repository at this point in the history
  • Loading branch information
jikkai committed Apr 2, 2024
1 parent 02cd695 commit a2dd33d
Show file tree
Hide file tree
Showing 13 changed files with 243 additions and 27 deletions.
1 change: 1 addition & 0 deletions packages/design/package.json
Expand Up @@ -83,6 +83,7 @@
"rc-picker": "^4.3.0",
"rc-segmented": "^2.3.0",
"rc-select": "^14.13.0",
"rc-textarea": "^1.6.3",
"rc-tooltip": "^6.2.0",
"rc-util": "^5.39.1",
"react-draggable": "^4.4.6",
Expand Down
1 change: 0 additions & 1 deletion packages/design/src/components/input/Input.stories.tsx
Expand Up @@ -45,7 +45,6 @@ export const InputSize = {
render() {
return (
<>
<Input size="mini" />
<Input size="small" />
<Input />
<Input size="large" />
Expand Down
3 changes: 1 addition & 2 deletions packages/design/src/components/input/Input.tsx
Expand Up @@ -59,7 +59,7 @@ export interface IInputProps extends Pick<InputProps, 'onFocus' | 'onBlur'> {
* The input size
* @default middle
*/
size?: 'mini' | 'small' | 'middle' | 'large';
size?: 'small' | 'middle' | 'large';

/**
* Whether the input is clearable
Expand Down Expand Up @@ -116,7 +116,6 @@ export function Input(props: IInputProps) {
}

const _className = clsx(className, {
[styles.inputAffixWrapperMini]: size === 'mini',
[styles.inputAffixWrapperSmall]: size === 'small',
[styles.inputAffixWrapperMiddle]: size === 'middle',
[styles.inputAffixWrapperLarge]: size === 'large',
Expand Down
4 changes: 1 addition & 3 deletions packages/design/src/components/input/InputWithSlot.tsx
Expand Up @@ -26,10 +26,8 @@ export interface IInputWithSlotProps extends IInputProps {
slot: React.ReactNode;
}

function getPaddingRightBySize(size?: 'mini' | 'small' | 'middle' | 'large'): number {
function getPaddingRightBySize(size?: 'small' | 'middle' | 'large'): number {
switch (size) {
case 'mini':
return 2;
case 'small':
return 6;
case 'middle':
Expand Down
17 changes: 7 additions & 10 deletions packages/design/src/components/input/index.module.less
Expand Up @@ -24,27 +24,24 @@

transition: all 0.15s;

&-mini {
display: inline-block;
padding: 2px var(--padding-lg);
font-size: var(--font-size-xs);
}

&-small {
display: inline-block;
padding: 6px var(--padding-lg);
font-size: var(--font-size-xs);
width: 136px;
padding: 4px var(--padding-sm);
font-size: var(--font-size-sm);
}

&-middle {
display: inline-block;
padding: 6px var(--padding-lg);
width: 200px;
padding: 5px var(--padding-sm);
font-size: var(--font-size-sm);
}

&-large {
display: inline-block;
padding: 8px var(--padding-lg);
width: 280px;
padding: 6px var(--padding-sm);
font-size: var(--font-size-sm);
}

Expand Down
47 changes: 47 additions & 0 deletions packages/design/src/components/textarea/Textarea.stories.tsx
@@ -0,0 +1,47 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { Meta } from '@storybook/react';
import React from 'react';

import { Textarea } from './Textarea';

const meta: Meta<typeof Textarea> = {
title: 'Components / Textarea',
component: Textarea,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
};

export default meta;

export const TextareaBasic = {
render() {
return (
<>
<Textarea autoSize={{ minRows: 4 }} />
</>
);
},
};

export const TextareaDisabled = {
render() {
return <Textarea disabled />;
},
};
111 changes: 111 additions & 0 deletions packages/design/src/components/textarea/Textarea.tsx
@@ -0,0 +1,111 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import clsx from 'clsx';
import type { TextAreaProps } from 'rc-textarea';
import RcTextarea from 'rc-textarea';
import React from 'react';

import styles from './index.module.less';

export interface ITextareaProps extends Pick<TextAreaProps, 'onFocus' | 'onBlur'> {
/**
* Whether the input is autoFocus
* @default false
*/
autoFocus?: boolean;

/**
* The input class name
*/
className?: string;

/**
* The input affix wrapper style
*/
style?: React.CSSProperties;

/**
* The input affix wrapper style
*/
autoSize?: boolean | { minRows?: number; maxRows?: number };

/**
* The input placeholder
*/
placeholder?: string;

/**
* The input content value
*/
value?: string;

/**
* Whether the input is disabled
* @default false
*/
disabled?: boolean;

/**
* Callback when user press a key
* @param e
*/
onKeyDown?: React.KeyboardEventHandler;

/**
* Callback when user input
* @param value
*/
onChange?: (value: string) => void;

}

export function Textarea(props: ITextareaProps) {
const {
autoFocus = false,
autoSize = false,
className,
placeholder,
value,
disabled = false,
onKeyDown,
onChange,
...rest
} = props;

function handleChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
const { value } = e.target;
onChange?.(value);
}

const _className = clsx(className, {
}, className);

return (
<RcTextarea
prefixCls={styles.textarea}
classNames={{ affixWrapper: _className }}
autoFocus={autoFocus}
autoSize={autoSize}
placeholder={placeholder}
value={value}
disabled={disabled}
onKeyDown={onKeyDown}
onChange={handleChange}
{...rest}
/>
);
}
24 changes: 24 additions & 0 deletions packages/design/src/components/textarea/index.module.less
@@ -0,0 +1,24 @@
@textarea-prefix-cls: textarea;

.@{textarea-prefix-cls} {
position: relative;

width: 280px;

padding: var(--padding-sm);

box-sizing: border-box;

border: 1px solid rgb(var(--border-color));
border-radius: var(--border-radius-base);

overflow: hidden;

transition: all 0.15s;

&:hover,
&:focus {
border-color: rgb(var(--primary-color));
outline: none;
}
}
17 changes: 17 additions & 0 deletions packages/design/src/components/textarea/index.ts
@@ -0,0 +1,17 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export { type ITextareaProps, Textarea } from './Textarea';
Expand Up @@ -23,17 +23,20 @@ import { CFNumberOperator,
CFSubRuleType,
CFTextOperator,
CFTimePeriodOperator,
createDefaultValue } from '@univerjs/sheets-conditional-formatting';
createDefaultValue,
} from '@univerjs/sheets-conditional-formatting';
import type {
IConditionalFormattingRuleConfig,
IHighlightCell,
INumberHighlightCell,
ITextHighlightCell,
ITimePeriodHighlightCell } from '@univerjs/sheets-conditional-formatting';
ITimePeriodHighlightCell,
} from '@univerjs/sheets-conditional-formatting';
import { ConditionalStyleEditor } from '../../conditional-style-editor';
import { Preview } from '../../preview';
import stylesBase from '../index.module.less';
import type { IStyleEditorProps } from './type';

import styles from './index.module.less';

const createOptionItem = (text: string, localeService: LocaleService) => ({ label: localeService.t(`sheet.cf.operator.${text}`), value: text });
Expand Down
16 changes: 11 additions & 5 deletions packages/ui/src/components/range-selector/RangeSelector.tsx
Expand Up @@ -39,7 +39,7 @@ export interface IRangeSelectorProps {
openForSheetUnitId?: Nullable<string>; // Configuring which workbook the selector defaults to opening in determines whether the ref includes a [unitId] prefix.
openForSheetSubUnitId?: Nullable<string>; // Configuring the default worksheet where the selector opens determines whether the ref includes a [unitId]sheet1 prefix.
width?: number; // The width of the selector.
size?: 'mini' | 'small' | 'middle' | 'large'; // The size of the selector.
size?: 'small' | 'middle' | 'large'; // The size of the selector.
placeholder?: string; // Placeholder text.
className?: string;
}
Expand Down Expand Up @@ -279,9 +279,7 @@ export function RangeSelector(props: IRangeSelectorProps) {
}

let height = 32;
if (size === 'mini') {
height = 24;
} else if (size === 'small') {
if (size === 'small') {
height = 28;
} else if (size === 'large') {
height = 36;
Expand Down Expand Up @@ -317,7 +315,15 @@ export function RangeSelector(props: IRangeSelectorProps) {
{rangeDataList.map((item, index) => (
<div key={index} className={styles.rangeSelectorModalContainer}>
<div style={{ width: rangeDataList.length === 1 ? '280px' : '252px' }} className={styles.rangeSelectorModalContainerInput}>
<Input className={currentInputIndex === index ? styles.rangeSelectorModalContainerInputActive : ((rangeDataList.length - 1 === index && currentInputIndex === -1) ? styles.rangeSelectorModalContainerInputActive : '')} placeholder={localeService.t('rangeSelector.placeHolder')} affixWrapperStyle={{ width: '100%' }} key={`input${index}`} onClick={() => setCurrentInputIndex(index)} value={item} onChange={(value) => changeItem(index, value)} />
<Input
className={currentInputIndex === index ? styles.rangeSelectorModalContainerInputActive : ((rangeDataList.length - 1 === index && currentInputIndex === -1) ? styles.rangeSelectorModalContainerInputActive : '')}
placeholder={localeService.t('rangeSelector.placeHolder')}
affixWrapperStyle={{ width: '100%' }}
key={`input${index}`}
onClick={() => setCurrentInputIndex(index)}
value={item}
onChange={(value) => changeItem(index, value)}
/>
</div>
<div style={{ display: rangeDataList.length === 1 ? 'none' : 'inline-block' }} className={styles.rangeSelectorModalContainerButton}>
<DeleteSingle onClick={() => removeItem(index)} />
Expand Down
1 change: 0 additions & 1 deletion packages/uniscript/package.json
Expand Up @@ -81,7 +81,6 @@
"@univerjs/design": "workspace:*",
"@univerjs/facade": "workspace:*",
"@univerjs/shared": "workspace:*",
"@univerjs/sheets": "workspace:*",
"@univerjs/ui": "workspace:*",
"@wendellhu/redi": "^0.13.0",
"less": "^4.2.0",
Expand Down
21 changes: 18 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a2dd33d

Please sign in to comment.