-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core-components-with-suffix): add hoc
- Loading branch information
Showing
8 changed files
with
351 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"name": "@alfalab/core-components-with-suffix", | ||
"version": "1.0.0", | ||
"description": "", | ||
"keywords": [], | ||
"license": "ISC", | ||
"main": "dist/index.js", | ||
"files": [ | ||
"dist" | ||
], | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"peerDependencies": { | ||
"classnames": "^2.2.6", | ||
"react": "^16.9.0" | ||
}, | ||
"dependencies": { | ||
"@alfalab/core-components-input": "^1.9.0", | ||
"@alfalab/core-components-portal": "^1.3.0", | ||
"react-merge-refs": "^1.1.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { Meta, Story, Props, Preview } from '@storybook/addon-docs/blocks'; | ||
import { text, select, boolean } from '@storybook/addon-knobs'; | ||
import { ComponentHeader } from 'storybook/blocks/component-header'; | ||
import Icon from '@alfalab/icons-glyph/StarMIcon'; | ||
|
||
import { Input } from '@alfalab/core-components-input'; | ||
import { withSuffix as WithSuffix } from './Component'; | ||
import { name, version } from '../package.json'; | ||
|
||
export const SuffixInput = WithSuffix(Input); | ||
|
||
|
||
<Meta | ||
title='Компоненты|HOCS' | ||
component={WithSuffix} | ||
parameters={{ 'theme-switcher': { themes: ['click'] } }} | ||
/> | ||
|
||
|
||
<!-- Canvas --> | ||
|
||
<Story name='withSuffix'> | ||
<SuffixInput | ||
suffix={text('suffix', ' ₽')} | ||
block={boolean('block', false)} | ||
clear={boolean('clear', false)} | ||
size={select('size', ['s', 'm', 'l'], 's')} | ||
disabled={boolean('disabled', false)} | ||
label={text('label', '')} | ||
placeholder={text('placeholder', '0 ₽')} | ||
rightAddons={boolean('rightAddons', false) && !text('error') && <Icon />} | ||
leftAddons={boolean('leftAddons', false) && <Icon />} | ||
bottomAddons={boolean('bottomAddons', false) && <span>bottom text</span>} | ||
/> | ||
</Story> | ||
|
||
|
||
<!-- Docs --> | ||
|
||
<ComponentHeader | ||
name='withSuffix' | ||
version={version} | ||
package='@alfalab/core-components-with-suffix' | ||
stage={1} | ||
/> | ||
|
||
```tsx | ||
import { withSuffix } from '@alfalab/core-components-with-suffix'; | ||
``` | ||
|
||
HOC, позволяющий закрепить определенный текст после значения инпута. | ||
|
||
Добавляет в переданный инпут доп. пропсу `suffix?: ReactNode` | ||
|
||
<Preview> | ||
{React.createElement(() => { | ||
const SuffixInput = WithSuffix(Input); | ||
return <SuffixInput suffix={<b> мес</b>} />; | ||
})} | ||
</Preview> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import React from 'react'; | ||
import { render } from '@testing-library/react'; | ||
import { Input } from '@alfalab/core-components-input'; | ||
|
||
import userEvent from '@testing-library/user-event'; | ||
import { withSuffix } from './index'; | ||
|
||
const SuffixInput = withSuffix(Input); | ||
|
||
describe('withSuffix', () => { | ||
describe('Snapshots tests', () => { | ||
it('should match snapshot', () => { | ||
expect(render(<SuffixInput value='10' suffix=' лет' />).container).toMatchSnapshot(); | ||
}); | ||
}); | ||
|
||
it('should forward ref to html input', () => { | ||
const inputRef = jest.fn(); | ||
const dataTestId = 'test-id'; | ||
render(<SuffixInput ref={inputRef} dataTestId={dataTestId} />); | ||
|
||
expect(inputRef.mock.calls[0][0].tagName).toBe('INPUT'); | ||
}); | ||
|
||
it('should render suffix when controlled', () => { | ||
const suffix = 'suffix'; | ||
const { container } = render(<SuffixInput value='10' suffix={suffix} />); | ||
|
||
expect(container.querySelector('.suffixVisible')).toBeInTheDocument(); | ||
}); | ||
|
||
it('should render suffix when uncontrolled', async () => { | ||
const dataTestId = 'test-id'; | ||
const suffix = 'suffix'; | ||
const { container, getByTestId } = render( | ||
<SuffixInput suffix={suffix} dataTestId={dataTestId} />, | ||
); | ||
|
||
const input = getByTestId(dataTestId) as HTMLInputElement; | ||
|
||
expect(container.querySelector('.suffixVisible')).not.toBeInTheDocument(); | ||
|
||
await userEvent.type(input, 'some value'); | ||
|
||
expect(container.querySelector('.suffixVisible')).toBeInTheDocument(); | ||
}); | ||
|
||
it('should set `suffixContainerClassName` class to root', () => { | ||
const className = 'test-class'; | ||
const { container } = render(<SuffixInput suffixContainerClassName={className} />); | ||
|
||
expect(container.getElementsByClassName(className).length).toBe(1); | ||
}); | ||
|
||
it('should unmount without errors', () => { | ||
const { unmount } = render(<SuffixInput value='10' suffix=' лет' />); | ||
|
||
expect(unmount).not.toThrowError(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import React, { | ||
useState, | ||
useCallback, | ||
useRef, | ||
FC, | ||
RefAttributes, | ||
Fragment, | ||
forwardRef, | ||
ReactNode, | ||
} from 'react'; | ||
|
||
import cn from 'classnames'; | ||
import mergeRefs from 'react-merge-refs'; | ||
import { Portal } from '@alfalab/core-components-portal'; | ||
import { InputProps } from '@alfalab/core-components-input'; | ||
|
||
import styles from './index.module.css'; | ||
|
||
export type withSuffixProps = InputProps & { | ||
/** | ||
* Дополнительный закрепленный текст справа от основного значения. | ||
* Например: value='85' suffix=' мес' -> 85 мес | ||
*/ | ||
suffix?: ReactNode; | ||
|
||
/** | ||
* Дополнительный класс для контейнера с суффиксом | ||
*/ | ||
suffixContainerClassName?: string; | ||
}; | ||
|
||
export const withSuffix = (Input: FC<InputProps & RefAttributes<HTMLInputElement>>) => | ||
forwardRef<HTMLInputElement, withSuffixProps>( | ||
( | ||
{ | ||
value, | ||
defaultValue, | ||
onChange, | ||
onClear, | ||
suffix = '', | ||
placeholder, | ||
className, | ||
disabled, | ||
suffixContainerClassName, | ||
...restProps | ||
}, | ||
ref, | ||
) => { | ||
const uncontrolled = value === undefined; | ||
|
||
const inputRef = useRef<HTMLInputElement>(null); | ||
|
||
const [stateValue, setStateValue] = useState(defaultValue || ''); | ||
|
||
const handleInputChange = useCallback<Required<InputProps>['onChange']>( | ||
(event, payload) => { | ||
if (onChange) { | ||
onChange(event, payload); | ||
} | ||
|
||
if (uncontrolled) { | ||
setStateValue(payload.value); | ||
} | ||
}, | ||
[onChange, uncontrolled], | ||
); | ||
|
||
const handleClear = useCallback<Required<InputProps>['onClear']>( | ||
event => { | ||
if (uncontrolled) { | ||
setStateValue(''); | ||
} | ||
|
||
if (onClear) { | ||
onClear(event); | ||
} | ||
}, | ||
[onClear, uncontrolled], | ||
); | ||
|
||
const getPortalContainer = useCallback( | ||
// TODO: Изменить сигнатуру getPortalContainer в Portal | ||
() => (inputRef.current as HTMLElement).parentElement as HTMLElement, | ||
[], | ||
); | ||
|
||
const visibleValue = uncontrolled ? stateValue : value; | ||
|
||
return ( | ||
<Fragment> | ||
<Input | ||
ref={mergeRefs([ref, inputRef])} | ||
value={visibleValue} | ||
disabled={disabled} | ||
onChange={handleInputChange} | ||
onClear={handleClear} | ||
placeholder={placeholder} | ||
className={cn(className, { | ||
[styles.suffixVisible]: Boolean(visibleValue), | ||
[styles.hasSuffix]: suffix, | ||
})} | ||
{...restProps} | ||
/> | ||
<Portal getPortalContainer={getPortalContainer}> | ||
<div className={cn(styles.suffixContainer, suffixContainerClassName)}> | ||
<span className={styles.spacer}>{visibleValue}</span> | ||
{suffix && ( | ||
<div className={cn(styles.suffix, { [styles.disabled]: disabled })}> | ||
{suffix} | ||
</div> | ||
)} | ||
</div> | ||
</Portal> | ||
</Fragment> | ||
); | ||
}, | ||
); |
42 changes: 42 additions & 0 deletions
42
packages/with-suffix/src/__snapshots__/Component.test.tsx.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`withSuffix Snapshots tests should match snapshot 1`] = ` | ||
<div> | ||
<div | ||
class="component s filled formControl suffixVisible hasSuffix" | ||
> | ||
<div | ||
class="inner" | ||
> | ||
<div | ||
class="inputWrapper" | ||
> | ||
<div | ||
class="input" | ||
> | ||
<input | ||
class="input" | ||
type="text" | ||
value="10" | ||
/> | ||
<div | ||
class="suffixContainer" | ||
> | ||
<span | ||
class="spacer" | ||
> | ||
10 | ||
</span> | ||
<div | ||
class="suffix" | ||
> | ||
лет | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
.suffixContainer { | ||
position: absolute; | ||
left: 0; | ||
top: 0; | ||
bottom: 0; | ||
align-items: center; | ||
display: none; | ||
pointer-events: none; | ||
} | ||
|
||
input:focus + .suffixContainer, | ||
.suffixVisible .suffixContainer { | ||
display: inline-flex; | ||
} | ||
|
||
.hasSuffix input:focus::placeholder { | ||
color: transparent; | ||
transition: none; | ||
} | ||
|
||
.spacer { | ||
visibility: hidden; | ||
flex-shrink: 0; | ||
white-space: pre; | ||
} | ||
|
||
.suffix { | ||
white-space: pre; | ||
} | ||
|
||
.disabled { | ||
/* TODO: заменить на --input-disabled-color */ | ||
color: var(--color-dark-indigo-60-flat); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './Component'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"include": ["src", "../../typings"], | ||
"extends": "../../tsconfig.json", | ||
"compilerOptions": { | ||
"outDir": "dist", | ||
"rootDirs": ["src"], | ||
"baseUrl": ".", | ||
"paths": { | ||
"@alfalab/core-components-*": ["../*/src"] | ||
} | ||
}, | ||
"references": [{ "path": "../input" }, { "path": "../portal" }] | ||
} |