From 07f281e1165088f9ba8ea056a6a0fbb8f8e27786 Mon Sep 17 00:00:00 2001 From: S2kael Date: Mon, 13 Feb 2023 11:51:52 +0700 Subject: [PATCH] [Issue-134] [Field] Init UI --- components/field/Field.tsx | 87 ++++++++++++ components/field/index.tsx | 9 ++ components/field/stories/field.stories.tsx | 155 +++++++++++++++++++++ components/field/style/index.tsx | 123 ++++++++++++++++ components/index.ts | 3 + components/theme/interface/components.ts | 2 + 6 files changed, 379 insertions(+) create mode 100644 components/field/Field.tsx create mode 100644 components/field/index.tsx create mode 100644 components/field/stories/field.stories.tsx create mode 100644 components/field/style/index.tsx diff --git a/components/field/Field.tsx b/components/field/Field.tsx new file mode 100644 index 000000000000..67c0d90379bb --- /dev/null +++ b/components/field/Field.tsx @@ -0,0 +1,87 @@ +import classNames from 'classnames'; +import React from 'react'; +import type { PresetBarShapeType } from '../_util/shapes'; +import { ConfigContext } from '../config-provider'; +import { NoFormStyle } from '../form/context'; +import { NoCompactStyle } from '../space/Compact'; +import useStyle from './style'; +import Typography from '../typography'; + +export type SelectModalSize = 'small' | 'medium'; +export interface FieldProps { + content: React.ReactNode; + prefixCls?: string; + className?: string; + size?: SelectModalSize; + shape?: PresetBarShapeType; + placeholder?: string; + background?: 'default' | 'transparent'; + width?: number | string; + label?: string; + prefix?: React.ReactNode; + suffix?: React.ReactNode; + maxLine?: number; +} + +const DEFAULT_PLACEHOLDER = 'Placeholder'; + +const Field = (props: FieldProps): JSX.Element => { + const { getPrefixCls } = React.useContext(ConfigContext); + + const { + prefixCls: customizePrefixCls, + className, + shape = 'default', + background = 'default', + width = '100%', + label = '', + suffix, + prefix, + size: inputSize = 'medium', + content, + placeholder, + maxLine = 1, + } = props; + + const prefixCls = getPrefixCls('field', customizePrefixCls); + // Style + const [wrapSSR, hashId] = useStyle(prefixCls); + + return wrapSSR( + + +
+ {label &&
{label}
} +
+ {prefix} +
+ + {content || placeholder || DEFAULT_PLACEHOLDER} + +
+ {suffix} +
+
+
+
, + ); +}; + +export default Field; diff --git a/components/field/index.tsx b/components/field/index.tsx new file mode 100644 index 000000000000..73e48171cd53 --- /dev/null +++ b/components/field/index.tsx @@ -0,0 +1,9 @@ +import OriginField from './Field'; + +export type { FieldProps } from './Field'; + +type FieldType = typeof OriginField; + +const Field = OriginField as FieldType; + +export default Field; diff --git a/components/field/stories/field.stories.tsx b/components/field/stories/field.stories.tsx new file mode 100644 index 000000000000..492b59ef85db --- /dev/null +++ b/components/field/stories/field.stories.tsx @@ -0,0 +1,155 @@ +import React, { useMemo } from 'react'; +import type { ComponentStory, ComponentMeta } from '@storybook/react'; +import SwAvatar from '../../sw-avatar'; +import Field from '../index'; +import type { FieldProps } from '../index'; + +interface WrapperProps extends FieldProps { + suffixType: number; + prefixType: number; +} + +const icon = ; + +const Wrapper = ({ suffixType = 1, prefixType = 0, ...args }: WrapperProps) => { + const additionalProps = useMemo((): Pick => { + const result: Pick = {}; + + switch (prefixType) { + case 1: + result.prefix = icon; + break; + default: + break; + } + + switch (suffixType) { + case 2: + result.suffix = icon; + break; + default: + break; + } + + return result; + }, [suffixType, prefixType]); + + return ( +
+ +
+ ); +}; + +export default { + title: 'Basic Components/Field', + component: Wrapper, + // More on argTypes: https://storybook.js.org/docs/react/api/argtypes + argTypes: { + title: { + type: 'string', + }, + placeholder: { + type: 'string', + }, + label: { + type: 'string', + if: { + arg: 'withLabel', + eq: true, + }, + }, + shape: { + control: 'radio', + options: ['default', 'square', 'round'], + }, + background: { + control: 'radio', + options: ['default', 'transparent'], + }, + size: { + control: 'select', + options: ['small', 'medium'], + if: { + arg: 'label', + eq: '', + }, + }, + disabled: { + type: 'boolean', + if: { + arg: 'withDisabled', + eq: true, + }, + }, + withLabel: { + control: false, + }, + suffixType: { + control: false, + }, + prefixType: { + control: false, + }, + maxLine: { + type: 'number', + defaultValue: 1, + }, + }, + // @ts-ignore +} as ComponentMeta; + +// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args +// @ts-ignore +const Template: ComponentStory = ({ ...args }) => ; + +const DEFAULT_ARGS = { + shape: 'default', + background: 'default', + size: 'medium', + placeholder: 'Placeholder', + content: + 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Adipisci amet corporis cumque deleniti dicta distinctio dolores ea est et eveniet ipsam iste molestiae non possimus, recusandae rem sint. Cumque, eum?', + disabled: false, + label: '', +}; +export const Default = Template.bind({}); + +Default.args = { + ...DEFAULT_ARGS, +}; + +export const WithLabel = Template.bind({}); + +WithLabel.args = { + ...DEFAULT_ARGS, + label: 'Label', + withLabel: true, +}; +export const CustomSuffix = Template.bind({}); + +CustomSuffix.args = { + ...DEFAULT_ARGS, + suffixType: 2, + label: 'Label', + withLabel: true, +}; + +export const CustomPrefix = Template.bind({}); + +CustomPrefix.args = { + ...DEFAULT_ARGS, + prefixType: 1, + label: 'Label', + withLabel: true, +}; + +export const FullCustom = Template.bind({}); + +FullCustom.args = { + ...DEFAULT_ARGS, + suffixType: 2, + prefixType: 1, + label: 'Label', + withLabel: true, +}; diff --git a/components/field/style/index.tsx b/components/field/style/index.tsx new file mode 100644 index 000000000000..831d43ffd833 --- /dev/null +++ b/components/field/style/index.tsx @@ -0,0 +1,123 @@ +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook, mergeToken } from '../../theme/internal'; + +/** Component only token. Which will handle additional calculation of alias token */ +export interface ComponentToken { + // Component token here +} + +export interface FieldToken extends FullToken<'Field'> {} +const genFieldStyle: GenerateStyle = (token) => { + const { componentCls } = token; + + return [ + { + [`${componentCls}-container`]: { + display: 'flex', + flexDirection: 'column', + gap: 8, + color: token.colorTextTertiary, + lineHeight: token.lineHeightLG, + overflow: 'hidden', + position: 'relative', + + [`${componentCls}-wrapper`]: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + gap: 8, + overflow: 'hidden', + + [`${componentCls}-content-wrapper`]: { + overflow: 'hidden', + flex: 1, + + [`${componentCls}-content`]: { + marginBottom: 0, + color: token.colorText, + }, + }, + }, + + [`&${componentCls}-placeholder`]: { + [`${componentCls}-wrapper`]: { + [`${componentCls}-content-wrapper`]: { + [`${componentCls}-content`]: { + color: token.colorTextLight4, + }, + }, + }, + }, + + [`${componentCls}-label`]: { + fontSize: token.fontSizeSM, + lineHeight: token.lineHeightSM, + color: token.colorTextLight4, + paddingLeft: token.paddingSM, + paddingRight: token.paddingSM, + paddingTop: token.paddingXXS, + top: token.paddingXXS, + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + overflow: 'hidden', + position: 'relative', + }, + + // Size + + [`&${componentCls}-size-small`]: { + [`${componentCls}-wrapper`]: { + padding: `${token.paddingContentVerticalSM}px ${token.paddingContentHorizontal}px`, + }, + }, + + [`&${componentCls}-size-medium`]: { + [`${componentCls}-wrapper`]: { + padding: `${token.paddingContentVertical}px ${token.paddingContentHorizontal}px`, + }, + }, + + [`&${componentCls}-with-label`]: { + [`${componentCls}-placeholder`]: { + color: token.colorText, + }, + + [`${componentCls}-wrapper`]: { + padding: `${token.paddingContentVertical - 2}px ${token.paddingSM}px ${ + token.paddingContentVertical + }px`, + }, + }, + + // Border + [`&${componentCls}-border-square`]: { + borderRadius: 0, + }, + + [`&${componentCls}-border-round`]: { + borderRadius: token.controlHeightLG + token.borderRadiusLG, + }, + + [`&${componentCls}-border-default`]: { + borderRadius: token.borderRadius, + }, + + // Background + [`&${componentCls}-bg-default`]: { + background: token.colorBgSecondary, + }, + + [`&${componentCls}-bg-transparent`]: { + background: 'transparent', + }, + }, + }, + ]; +}; + +// ============================== Export ============================== +export default genComponentStyleHook('Field', (token) => { + const fieldToken = mergeToken(token); + return [genFieldStyle(fieldToken)]; +}); diff --git a/components/index.ts b/components/index.ts index b2c49e676680..e4c63370426d 100644 --- a/components/index.ts +++ b/components/index.ts @@ -156,6 +156,9 @@ export { default as QRCode } from './qrcode'; export type { QRCodeProps, QRPropsCanvas } from './qrcode/interface'; export { default as version } from './version'; +export { default as Field } from './field'; +export type { FieldProps } from './field'; + export { default as ActivityIndicator } from './activity-indicator'; export type { ActivityIndicatorProps } from './activity-indicator'; export { default as BackgroundIcon } from './background-icon'; diff --git a/components/theme/interface/components.ts b/components/theme/interface/components.ts index 7c22dc7228b7..f94368f5e741 100644 --- a/components/theme/interface/components.ts +++ b/components/theme/interface/components.ts @@ -14,6 +14,7 @@ import type { ComponentToken as ActivityIndicatorComponentToken } from '../../ac import type { ComponentToken as BackTopComponentToken } from '../../back-top/style'; import type { ComponentToken as ButtonComponentToken } from '../../button/style'; import type { ComponentToken as FloatButtonComponentToken } from '../../float-button/style'; +import type { ComponentToken as FieldComponentToken } from '../../field/style'; import type { ComponentToken as CalendarComponentToken } from '../../calendar/style'; import type { ComponentToken as CardComponentToken } from '../../card/style'; import type { ComponentToken as CarouselComponentToken } from '../../carousel/style'; @@ -106,6 +107,7 @@ export interface ComponentTokenMap { Drawer?: DrawerComponentToken; Dropdown?: DropdownComponentToken; Empty?: EmptyComponentToken; + Field?: FieldComponentToken; FloatButton?: FloatButtonComponentToken; Form?: {}; Grid?: {};