Skip to content

Commit 4c7728c

Browse files
committed
feat(Input): Add prefix/suffix support
#10
1 parent ece2f46 commit 4c7728c

File tree

10 files changed

+325
-22
lines changed

10 files changed

+325
-22
lines changed

packages/fluent-ui-icons/src/utils/createIcon.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export interface JSX {
88

99
function createElement(
1010
jsx: JSX,
11-
otherProps: React.HTMLAttributes<SVGElement> = {}
11+
otherProps: React.HTMLAttributes<SVGElement> & React.RefAttributes<SVGElement> = {}
1212
): React.ReactSVGElement {
1313
const { type, props, children: childrenAttr } = jsx
1414
const children = childrenAttr.map((child): React.ReactSVGElement[] =>
@@ -36,18 +36,23 @@ function toHump(name: string, capitalized: boolean = true): string {
3636
: name.replace(/-(\w)/g, (all, letter): string => letter.toUpperCase())
3737
}
3838

39-
function createIcon(jsx: JSX, componentName: string): React.FC {
40-
const Icon: React.FC = ({ ...rest }: React.HTMLAttributes<SVGElement>): React.ReactElement => {
41-
const style = {
42-
width: '1em',
43-
height: '1em',
44-
display: 'inline-block',
45-
fontSize: 'inherit',
46-
color: 'inherit',
47-
fill: 'currentColor'
39+
function createIcon(
40+
jsx: JSX,
41+
componentName: string
42+
): React.ForwardRefExoticComponent<React.RefAttributes<SVGElement>> {
43+
const Icon = React.forwardRef<SVGElement>(
44+
({ ...rest }: React.HTMLAttributes<SVGElement>, ref): React.ReactElement => {
45+
const style = {
46+
width: '1em',
47+
height: '1em',
48+
display: 'inline-block',
49+
fontSize: 'inherit',
50+
color: 'inherit',
51+
fill: 'currentColor'
52+
}
53+
return createElement(jsx, { ...rest, style, ref })
4854
}
49-
return createElement(jsx, { ...rest, style })
50-
}
55+
)
5156
Icon.displayName = `RemixIcon${toHump(componentName)}`
5257
return Icon
5358
}

packages/fluent-ui.com/src/docs/components/Input/Input.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,12 @@ Show a button to clear your input.
8080
)
8181
}
8282
```
83+
84+
## PrefixAndSuffix
85+
86+
```jsx
87+
<>
88+
<Input prefix="¥" suffix="RMB" />
89+
<Input prefix={<Icon.UserSmileLine />} suffix={<Icon.InformationLine />} />
90+
</>
91+
```

packages/fluent-ui.com/src/docs/components/Input/Input.zh.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,12 @@ langKey: "zh"
8080
)
8181
}
8282
```
83+
84+
## 前置和后置
85+
86+
```jsx
87+
<>
88+
<Input prefix="¥" suffix="RMB" />
89+
<Input prefix={<Icon.UserSmileLine />} suffix={<Icon.InformationLine />} />
90+
</>
91+
```

packages/fluent-ui.com/src/docs/components/Input/api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ import { Input } from '@fluent-ui/core'
2424
| cleared | boolean | false | Whether to display a button to clear the value of the `input` element |
2525
| password | boolean | | The `input` element will be rendered as `<input type="password" />` |
2626
| error | boolean | | If `true`, the `input` will indicate an error. |
27+
| prefix | React.ReactNode | null | Pre-content input box. |
28+
| suffix | React.ReactNode | null | Rear content input box. |

packages/fluent-ui.com/src/docs/components/Input/api.zh.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ import { Input } from '@fluent-ui/core'
2424
| cleared | boolean | false | 是否显示一个按钮清除输入元素的值。 |
2525
| password | boolean | | `input` 元素将被渲染为 `<input type="password" />` |
2626
| error | boolean | | 如果 `true`, `input` 将会表示为错误状态。 |
27+
| prefix | React.ReactNode | null | 输入框的前置内容。 |
28+
| suffix | React.ReactNode | null | 输入框的后置内容。 |

packages/fluent-ui/src/Input/Input.styled.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Style, Styles } from 'jss'
22
import { Theme } from '../styles'
3-
import { InputClassProps } from './Input.type'
3+
import { InputClassProps, InputProps } from './Input.type'
44

55
const wrapper: Style = {
66
display: 'inline-block',
@@ -9,15 +9,14 @@ const wrapper: Style = {
99
font: 'inherit'
1010
}
1111

12-
const root = (theme: Theme) => ({
12+
const root = (theme: Theme): Style => ({
1313
outline: 'none',
1414
font: 'inherit',
1515
width: '100%',
1616
borderRadius: 2,
1717
border: '2px solid',
1818
borderColor: theme.colors!.standard!.default,
1919
transition: theme.transitions!.input,
20-
// ${th.size('medium.input')},
2120
'&:hover': {
2221
borderColor: theme.colors!.standard!.dark1
2322
},
@@ -30,7 +29,13 @@ const root = (theme: Theme) => ({
3029
cursor: 'not-allowed',
3130
pointerEvents: 'none'
3231
},
33-
...theme.sizes!.medium!.input
32+
...theme.sizes!.medium!.input,
33+
'&:not(:first-child)': {
34+
paddingLeft: 30
35+
},
36+
'&:not(:last-child)': {
37+
paddingRight: 30
38+
}
3439
})
3540
const error = (theme: Theme): Style => ({
3641
borderColor: theme.colors!.error!.default,
@@ -59,9 +64,29 @@ const clearedIcon = (theme: Theme): Style => ({
5964
}
6065
})
6166

67+
const extra = (theme: Theme): Style => ({
68+
position: 'absolute',
69+
top: '50%',
70+
transform: 'translateY(-50%)',
71+
display: 'flex',
72+
justifyContent: 'center',
73+
alignItems: 'center',
74+
color: theme.colors!.standard!.transparent3
75+
})
76+
const prefix = (theme: Theme): Style => ({
77+
...extra(theme),
78+
left: 12
79+
})
80+
const suffix = (theme: Theme): Style => (props: InputProps): Style => ({
81+
...extra(theme),
82+
right: 12 + (props.suffix && !!props.value ? 30 : 0)
83+
})
84+
6285
export const styles = (theme: Theme): Styles<InputClassProps> => ({
6386
root: root(theme),
6487
error: error(theme),
6588
wrapper,
66-
clearedIcon: clearedIcon(theme)
89+
clearedIcon: clearedIcon(theme),
90+
prefix: prefix(theme),
91+
suffix: suffix(theme)
6792
})

packages/fluent-ui/src/Input/Input.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const Input: React.FC<InputProps> = React.forwardRef<HTMLInputElement, InputProp
2424
cleared,
2525
password,
2626
error,
27+
prefix = null,
28+
suffix = null,
2729
...rest
2830
} = props
2931

@@ -55,14 +57,15 @@ const Input: React.FC<InputProps> = React.forwardRef<HTMLInputElement, InputProp
5557
(): object =>
5658
cleared
5759
? {
58-
paddingRight: clearedHeight
60+
paddingRight: clearedHeight + (suffix ? 30 : 0)
5961
}
6062
: {},
61-
[cleared, clearedHeight]
63+
[cleared, clearedHeight, suffix]
6264
)
6365

6466
return (
6567
<div className={classes.wrapper} style={style}>
68+
{prefix && <div className={classes.prefix}>{prefix}</div>}
6669
<input
6770
className={className}
6871
ref={ref}
@@ -74,6 +77,7 @@ const Input: React.FC<InputProps> = React.forwardRef<HTMLInputElement, InputProp
7477
style={clearedInputStyle}
7578
{...rest}
7679
/>
80+
{suffix && <div className={classes.suffix}>{suffix}</div>}
7781
{cleared && (
7882
<Transition visible={!!value} wrapper={false}>
7983
<span
@@ -97,4 +101,9 @@ Input.displayName = `F${name}`
97101

98102
Input.propTypes = InputPropTypes
99103

104+
Input.defaultProps = {
105+
prefix: null,
106+
suffix: null
107+
}
108+
100109
export default Input

packages/fluent-ui/src/Input/Input.type.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,23 @@ import * as React from 'react'
22
import * as PropTypes from 'prop-types'
33
import { StandardProps } from '..'
44

5-
export type InputClassProps = 'root' | 'error' | 'wrapper' | 'clearedIcon'
5+
export type InputClassProps = 'root' | 'error' | 'wrapper' | 'clearedIcon' | 'prefix' | 'suffix'
66

77
export interface InputProps
8-
extends StandardProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement, 'onChange'> {
8+
extends StandardProps<
9+
React.InputHTMLAttributes<HTMLInputElement>,
10+
HTMLInputElement,
11+
'onChange' | 'prefix'
12+
> {
913
value?: string
1014
onChange?: (value: string) => void
1115
placeholder?: string
1216
disabled?: boolean
1317
cleared?: boolean
1418
password?: boolean
1519
error?: boolean
20+
prefix?: React.ReactNode
21+
suffix?: React.ReactNode
1622
}
1723

1824
export const InputPropTypes = {
@@ -22,5 +28,7 @@ export const InputPropTypes = {
2228
disabled: PropTypes.bool,
2329
cleared: PropTypes.bool,
2430
password: PropTypes.bool,
25-
error: PropTypes.bool
31+
error: PropTypes.bool,
32+
prefix: PropTypes.node,
33+
suffix: PropTypes.node
2634
}

packages/fluent-ui/src/Input/__tests__/Input.test.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,18 @@ describe('Input', (): void => {
7676
expect(ref.current as HTMLInputElement).toHaveClass(classes.error)
7777
expect(sheets.toString()).toMatchSnapshot()
7878
})
79+
80+
test('should be support prefix', (): void => {
81+
const { getByText, sheets } = render(<Input prefix={text} />)
82+
expect(getByText(text)).toBeInTheDocument()
83+
expect(getByText(text)).toHaveClass(classes.prefix)
84+
expect(sheets.toString()).toMatchSnapshot()
85+
})
86+
87+
test('should be support suffix', (): void => {
88+
const { getByText, sheets } = render(<Input suffix={text} />)
89+
expect(getByText(text)).toBeInTheDocument()
90+
expect(getByText(text)).toHaveClass(classes.suffix)
91+
expect(sheets.toString()).toMatchSnapshot()
92+
})
7993
})

0 commit comments

Comments
 (0)