mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
refactor(infra): directory structure (#4615)
This commit is contained in:
39
packages/frontend/component/src/ui/input/index.stories.tsx
Normal file
39
packages/frontend/component/src/ui/input/index.stories.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { expect } from '@storybook/jest';
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { Input } from '.';
|
||||
|
||||
export default {
|
||||
title: 'AFFiNE/Input',
|
||||
component: Input,
|
||||
} satisfies Meta<typeof Input>;
|
||||
|
||||
export const Basic: StoryFn<typeof Input> = () => {
|
||||
return <Input data-testid="test-input" defaultValue="test" />;
|
||||
};
|
||||
|
||||
Basic.play = async ({ canvasElement }) => {
|
||||
const element = within(canvasElement);
|
||||
const item = element.getByTestId('test-input') as HTMLInputElement;
|
||||
expect(item).toBeTruthy();
|
||||
expect(item.value).toBe('test');
|
||||
userEvent.clear(item);
|
||||
userEvent.type(item, 'test 2');
|
||||
expect(item.value).toBe('test 2');
|
||||
};
|
||||
|
||||
export const DynamicHeight: StoryFn<typeof Input> = () => {
|
||||
return <Input width={200} data-testid="test-input" />;
|
||||
};
|
||||
|
||||
DynamicHeight.play = async ({ canvasElement }) => {
|
||||
const element = within(canvasElement);
|
||||
const item = element.getByTestId('test-input') as HTMLInputElement;
|
||||
expect(item).toBeTruthy();
|
||||
expect(item.getBoundingClientRect().width).toBe(200);
|
||||
};
|
||||
|
||||
export const NoBorder: StoryFn<typeof Input> = () => {
|
||||
return <Input noBorder={true} data-testid="test-input" />;
|
||||
};
|
||||
3
packages/frontend/component/src/ui/input/index.ts
Normal file
3
packages/frontend/component/src/ui/input/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './input';
|
||||
import { Input } from './input';
|
||||
export default Input;
|
||||
123
packages/frontend/component/src/ui/input/input.tsx
Normal file
123
packages/frontend/component/src/ui/input/input.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||
import clsx from 'clsx';
|
||||
import type {
|
||||
ChangeEvent,
|
||||
CSSProperties,
|
||||
FocusEvent,
|
||||
FocusEventHandler,
|
||||
ForwardedRef,
|
||||
InputHTMLAttributes,
|
||||
KeyboardEvent,
|
||||
KeyboardEventHandler,
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
import { forwardRef, useCallback, useState } from 'react';
|
||||
|
||||
import { input, inputWrapper, widthVar } from './style.css';
|
||||
|
||||
export type InputProps = {
|
||||
disabled?: boolean;
|
||||
width?: CSSProperties['width'];
|
||||
onChange?: (value: string) => void;
|
||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
|
||||
noBorder?: boolean;
|
||||
status?: 'error' | 'success' | 'warning' | 'default';
|
||||
size?: 'default' | 'large' | 'extraLarge';
|
||||
preFix?: ReactNode;
|
||||
endFix?: ReactNode;
|
||||
type?: HTMLInputElement['type'];
|
||||
inputStyle?: CSSProperties;
|
||||
onEnter?: () => void;
|
||||
} & Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'size'>;
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
||||
{
|
||||
disabled,
|
||||
width,
|
||||
onChange: propsOnChange,
|
||||
noBorder = false,
|
||||
className,
|
||||
status = 'default',
|
||||
style = {},
|
||||
inputStyle = {},
|
||||
size = 'default',
|
||||
onFocus,
|
||||
onBlur,
|
||||
preFix,
|
||||
endFix,
|
||||
onEnter,
|
||||
onKeyDown,
|
||||
...otherProps
|
||||
}: InputProps,
|
||||
ref: ForwardedRef<HTMLInputElement>
|
||||
) {
|
||||
const [isFocus, setIsFocus] = useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(inputWrapper, className, {
|
||||
// status
|
||||
disabled: disabled,
|
||||
'no-border': noBorder,
|
||||
focus: isFocus,
|
||||
// color
|
||||
error: status === 'error',
|
||||
success: status === 'success',
|
||||
warning: status === 'warning',
|
||||
default: status === 'default',
|
||||
// size
|
||||
large: size === 'large',
|
||||
'extra-large': size === 'extraLarge',
|
||||
})}
|
||||
style={{
|
||||
...assignInlineVars({
|
||||
[widthVar]: width ? `${width}px` : '100%',
|
||||
}),
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
{preFix}
|
||||
<input
|
||||
className={clsx(input, {
|
||||
large: size === 'large',
|
||||
'extra-large': size === 'extraLarge',
|
||||
})}
|
||||
ref={ref}
|
||||
disabled={disabled}
|
||||
style={inputStyle}
|
||||
onFocus={useCallback(
|
||||
(e: FocusEvent<HTMLInputElement>) => {
|
||||
setIsFocus(true);
|
||||
onFocus?.(e);
|
||||
},
|
||||
[onFocus]
|
||||
)}
|
||||
onBlur={useCallback(
|
||||
(e: FocusEvent<HTMLInputElement>) => {
|
||||
setIsFocus(false);
|
||||
onBlur?.(e);
|
||||
},
|
||||
[onBlur]
|
||||
)}
|
||||
onChange={useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
propsOnChange?.(e.target.value);
|
||||
},
|
||||
[propsOnChange]
|
||||
)}
|
||||
onKeyDown={useCallback(
|
||||
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
onEnter?.();
|
||||
}
|
||||
onKeyDown?.(e);
|
||||
},
|
||||
[onKeyDown, onEnter]
|
||||
)}
|
||||
{...otherProps}
|
||||
/>
|
||||
{endFix}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
85
packages/frontend/component/src/ui/input/style.css.ts
Normal file
85
packages/frontend/component/src/ui/input/style.css.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { createVar, style } from '@vanilla-extract/css';
|
||||
|
||||
export const widthVar = createVar('widthVar');
|
||||
|
||||
export const inputWrapper = style({
|
||||
vars: {
|
||||
[widthVar]: '100%',
|
||||
},
|
||||
width: widthVar,
|
||||
height: 28,
|
||||
padding: '4px 10px',
|
||||
color: 'var(--affine-icon-color)',
|
||||
border: '1px solid var(--affine-border-color)',
|
||||
backgroundColor: 'var(--affine-white-10)',
|
||||
borderRadius: 8,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
// icon size
|
||||
fontSize: '16px',
|
||||
|
||||
selectors: {
|
||||
'&.no-border': {
|
||||
border: 'unset',
|
||||
},
|
||||
// size
|
||||
'&.large': {
|
||||
height: 32,
|
||||
// icon size
|
||||
fontSize: '20px',
|
||||
},
|
||||
'&.extra-large': {
|
||||
height: 40,
|
||||
padding: '8px 10px',
|
||||
// icon size
|
||||
fontSize: '20px',
|
||||
},
|
||||
// color
|
||||
'&.disabled': {
|
||||
background: 'var(--affine-hover-color)',
|
||||
},
|
||||
'&.error': {
|
||||
borderColor: 'var(--affine-error-color)',
|
||||
},
|
||||
'&.success': {
|
||||
borderColor: 'var(--affine-success-color)',
|
||||
},
|
||||
'&.warning': {
|
||||
borderColor: 'var(--affine-warning-color)',
|
||||
},
|
||||
'&.default.focus': {
|
||||
borderColor: 'var(--affine-primary-color)',
|
||||
boxShadow: 'var(--affine-active-shadow)',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const input = style({
|
||||
width: '0',
|
||||
flex: 1,
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
lineHeight: '20px',
|
||||
fontWeight: '500',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
boxSizing: 'border-box',
|
||||
// prevent default style
|
||||
WebkitAppearance: 'none',
|
||||
WebkitTapHighlightColor: 'transparent',
|
||||
outline: 'none',
|
||||
border: 'none',
|
||||
|
||||
selectors: {
|
||||
'&::placeholder': {
|
||||
color: 'var(--affine-placeholder-color)',
|
||||
},
|
||||
'&:disabled': {
|
||||
color: 'var(--affine-text-disable-color)',
|
||||
},
|
||||
'&.large, &.extra-large': {
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
lineHeight: '24px',
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user