The formatted category accepts input of pre-defined formats (email, url, telephone number). The option types for components have been fixed because of `uncheckedLabel` prop not present before, which was introduced for ToggleSwitch.master
@@ -2,12 +2,22 @@ | |||||
- [X] `ActionButton` | - [X] `ActionButton` | ||||
# Code | |||||
- [ ] `CodeBlock` | |||||
- [ ] `CodeInput` | |||||
- [ ] `VerificationCodeInput` | |||||
# Color | # Color | ||||
- [ ] `ColorSlider` | - [ ] `ColorSlider` | ||||
- [ ] `Palette` | - [ ] `Palette` | ||||
- [ ] `Swatch` | - [ ] `Swatch` | ||||
# DataGrid | |||||
- [ ] `DataGridView` | |||||
# File | # File | ||||
- [ ] `FileArea` | - [ ] `FileArea` | ||||
@@ -15,10 +25,10 @@ | |||||
# Formatted | # Formatted | ||||
- [X] `EmailInput` | |||||
- [ ] `SuggestionInput` | - [ ] `SuggestionInput` | ||||
- [ ] `EmailInput` | |||||
- [ ] `TelInput` | |||||
- [ ] `UrlInput` | |||||
- [X] `TelInput` | |||||
- [X] `UrlInput` | |||||
# Freeform | # Freeform | ||||
@@ -34,6 +44,10 @@ | |||||
- [X] `Badge` | - [X] `Badge` | ||||
# MIDI | |||||
- [ ] `MusicalKeyboard` | |||||
# Navigation | # Navigation | ||||
- [ ] `Breadcrumb` | - [ ] `Breadcrumb` | ||||
@@ -59,6 +73,14 @@ | |||||
- [X] `ToggleSwitch` | - [X] `ToggleSwitch` | ||||
- [X] `ToggleTickBox` | - [X] `ToggleTickBox` | ||||
# Rating | |||||
- [ ] `Rating` | |||||
# RichText | |||||
- [ ] `WysiwygTextInput` | |||||
# Temporal | # Temporal | ||||
- [ ] `Calendar` | - [ ] `Calendar` | ||||
@@ -68,3 +90,7 @@ | |||||
- [ ] `MonthDaySpinner` | - [ ] `MonthDaySpinner` | ||||
- [ ] `TimeSpinner` | - [ ] `TimeSpinner` | ||||
- [ ] `YearMonthSpinner` | - [ ] `YearMonthSpinner` | ||||
# Tree | |||||
- [ ] `TreeView` |
@@ -0,0 +1,210 @@ | |||||
import * as React from 'react'; | |||||
import { | |||||
render, | |||||
screen | |||||
} from '@testing-library/react'; | |||||
import '@testing-library/jest-dom'; | |||||
import userEvent from '@testing-library/user-event'; | |||||
import * as TextControlBase from '@tesseract-design/web-base-textcontrol'; | |||||
import { | |||||
EmailAddressInput, | |||||
} from '.'; | |||||
jest.mock('@tesseract-design/web-base-textcontrol'); | |||||
describe('EmailAddressInput', () => { | |||||
it('should render a textbox', () => { | |||||
render( | |||||
<EmailAddressInput /> | |||||
); | |||||
const textbox = screen.getByRole('textbox'); | |||||
expect(textbox).toBeInTheDocument(); | |||||
expect(textbox).toHaveProperty('type', 'email'); | |||||
}); | |||||
it('should render a border', () => { | |||||
render( | |||||
<EmailAddressInput | |||||
border | |||||
/> | |||||
); | |||||
const border = screen.getByTestId('border'); | |||||
expect(border).toBeInTheDocument(); | |||||
}); | |||||
it('should render a label', () => { | |||||
render( | |||||
<EmailAddressInput | |||||
label="foo" | |||||
/> | |||||
); | |||||
const textbox = screen.getByLabelText('foo'); | |||||
expect(textbox).toBeInTheDocument(); | |||||
const label = screen.getByTestId('label'); | |||||
expect(label).toHaveTextContent('foo'); | |||||
}); | |||||
it('should render a hidden label', () => { | |||||
render( | |||||
<EmailAddressInput | |||||
label="foo" | |||||
hiddenLabel | |||||
/> | |||||
); | |||||
const textbox = screen.getByLabelText('foo'); | |||||
expect(textbox).toBeInTheDocument(); | |||||
const label = screen.queryByTestId('label'); | |||||
expect(label).toBeNull(); | |||||
}); | |||||
it('should render a hint', () => { | |||||
render( | |||||
<EmailAddressInput | |||||
hint="foo" | |||||
/> | |||||
); | |||||
const hint = screen.getByTestId('hint'); | |||||
expect(hint).toBeInTheDocument(); | |||||
}); | |||||
it('should render an indicator', () => { | |||||
render( | |||||
<EmailAddressInput | |||||
indicator={ | |||||
<div data-testid="indicator" /> | |||||
} | |||||
/> | |||||
); | |||||
const indicator = screen.getByTestId('indicator'); | |||||
expect(indicator).toBeInTheDocument(); | |||||
}); | |||||
describe.each([ | |||||
TextControlBase.TextControlSize.SMALL, | |||||
TextControlBase.TextControlSize.MEDIUM, | |||||
TextControlBase.TextControlSize.LARGE, | |||||
])('on %s size', (size) => { | |||||
it('should render input styles', () => { | |||||
render( | |||||
<EmailAddressInput | |||||
size={size} | |||||
/> | |||||
); | |||||
expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render hint styles', () => { | |||||
render( | |||||
<EmailAddressInput | |||||
size={size} | |||||
hint="hint" | |||||
/> | |||||
); | |||||
expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render indicator styles', () => { | |||||
render( | |||||
<EmailAddressInput | |||||
size={size} | |||||
indicator={ | |||||
<div data-testid="indicator" /> | |||||
} | |||||
/> | |||||
); | |||||
expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
}); | |||||
it('should render a block textbox', () => { | |||||
render( | |||||
<EmailAddressInput | |||||
block | |||||
/> | |||||
); | |||||
expect(TextControlBase.Root).toBeCalledWith(expect.objectContaining({ | |||||
block: true, | |||||
})); | |||||
}); | |||||
describe.each([ | |||||
TextControlBase.TextControlStyle.DEFAULT, | |||||
TextControlBase.TextControlStyle.ALTERNATE, | |||||
])('on %s style', (style) => { | |||||
it('should render input styles', () => { | |||||
render( | |||||
<EmailAddressInput | |||||
style={style} | |||||
/> | |||||
); | |||||
expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
it('should render hint styles', () => { | |||||
render( | |||||
<EmailAddressInput | |||||
style={style} | |||||
hint="hint" | |||||
/> | |||||
); | |||||
expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
it('should render indicator styles', () => { | |||||
render( | |||||
<EmailAddressInput | |||||
style={style} | |||||
indicator={ | |||||
<div | |||||
data-testid="indicator" | |||||
/> | |||||
} | |||||
/> | |||||
); | |||||
expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
}); | |||||
it('should handle change events', () => { | |||||
const onChange = jest.fn(); | |||||
render( | |||||
<EmailAddressInput | |||||
onChange={onChange} | |||||
/> | |||||
); | |||||
const textbox: HTMLInputElement = screen.getByRole('textbox'); | |||||
userEvent.type(textbox, 'foobar'); | |||||
expect(onChange).toBeCalled(); | |||||
}); | |||||
it('should handle input events', () => { | |||||
const onInput = jest.fn(); | |||||
render( | |||||
<EmailAddressInput | |||||
onInput={onInput} | |||||
/> | |||||
); | |||||
const textbox: HTMLInputElement = screen.getByTestId('input'); | |||||
userEvent.type(textbox, 'foobar'); | |||||
expect(onInput).toBeCalled(); | |||||
}); | |||||
}); |
@@ -0,0 +1,124 @@ | |||||
import * as React from 'react'; | |||||
import * as TextControlBase from '@tesseract-design/web-base-textcontrol'; | |||||
export type EmailAddressInputProps = Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'style'> & { | |||||
/** | |||||
* Short textual description indicating the nature of the component's value. | |||||
*/ | |||||
label?: React.ReactNode, | |||||
/** | |||||
* Short textual description as guidelines for valid input values. | |||||
*/ | |||||
hint?: React.ReactNode, | |||||
/** | |||||
* Size of the component. | |||||
*/ | |||||
size?: TextControlBase.TextControlSize, | |||||
/** | |||||
* Additional description, usually graphical, indicating the nature of the component's value. | |||||
*/ | |||||
indicator?: React.ReactNode, | |||||
/** | |||||
* Should the component display a border? | |||||
*/ | |||||
border?: boolean, | |||||
/** | |||||
* Should the component occupy the whole width of its parent? | |||||
*/ | |||||
block?: boolean, | |||||
/** | |||||
* Style of the component. | |||||
*/ | |||||
style?: TextControlBase.TextControlStyle, | |||||
/** | |||||
* Is the label hidden? | |||||
*/ | |||||
hiddenLabel?: boolean, | |||||
} | |||||
/** | |||||
* Component for inputting email address values. | |||||
*/ | |||||
export const EmailAddressInput = React.forwardRef<HTMLInputElement, EmailAddressInputProps>( | |||||
( | |||||
{ | |||||
label = '', | |||||
hint = '', | |||||
indicator = null, | |||||
size = TextControlBase.TextControlSize.MEDIUM, | |||||
border = false, | |||||
block = false, | |||||
style = TextControlBase.TextControlStyle.DEFAULT, | |||||
hiddenLabel = false, | |||||
type: _type, | |||||
className: _className, | |||||
placeholder: _placeholder, | |||||
as: _as, | |||||
...etcProps | |||||
}: EmailAddressInputProps, | |||||
ref, | |||||
) => { | |||||
const styleArgs = React.useMemo<TextControlBase.TextControlBaseArgs>(() => ({ | |||||
block, | |||||
border, | |||||
size, | |||||
indicator: Boolean(indicator), | |||||
style, | |||||
resizable: false, | |||||
predefinedValues: false, | |||||
}), [block, border, size, indicator, style]); | |||||
return ( | |||||
<div | |||||
className={TextControlBase.Root(styleArgs)} | |||||
> | |||||
<input | |||||
{...etcProps} | |||||
className={TextControlBase.Input(styleArgs)} | |||||
ref={ref} | |||||
aria-label={label} | |||||
type="email" | |||||
data-testid="input" | |||||
/> | |||||
{ | |||||
border && ( | |||||
<span | |||||
data-testid="border" | |||||
/> | |||||
) | |||||
} | |||||
{ | |||||
label && !hiddenLabel && ( | |||||
<div | |||||
data-testid="label" | |||||
className={TextControlBase.LabelWrapper(styleArgs)} | |||||
> | |||||
{label} | |||||
</div> | |||||
) | |||||
} | |||||
{hint && ( | |||||
<div | |||||
className={TextControlBase.HintWrapper(styleArgs)} | |||||
data-testid="hint" | |||||
> | |||||
<div | |||||
className={TextControlBase.Hint()} | |||||
> | |||||
{hint} | |||||
</div> | |||||
</div> | |||||
)} | |||||
{indicator && ( | |||||
<div | |||||
className={TextControlBase.IndicatorWrapper(styleArgs)} | |||||
> | |||||
{indicator} | |||||
</div> | |||||
)} | |||||
</div> | |||||
); | |||||
} | |||||
); | |||||
EmailAddressInput.displayName = 'EmailAddressInput'; |
@@ -0,0 +1,210 @@ | |||||
import * as React from 'react'; | |||||
import { | |||||
render, | |||||
screen | |||||
} from '@testing-library/react'; | |||||
import '@testing-library/jest-dom'; | |||||
import userEvent from '@testing-library/user-event'; | |||||
import * as TextControlBase from '@tesseract-design/web-base-textcontrol'; | |||||
import { | |||||
PhoneNumberInput, | |||||
} from '.'; | |||||
jest.mock('@tesseract-design/web-base-textcontrol'); | |||||
describe('PhoneNumberInput', () => { | |||||
it('should render a textbox', () => { | |||||
render( | |||||
<PhoneNumberInput /> | |||||
); | |||||
const textbox = screen.getByRole('textbox'); | |||||
expect(textbox).toBeInTheDocument(); | |||||
expect(textbox).toHaveProperty('type', 'tel'); | |||||
}); | |||||
it('should render a border', () => { | |||||
render( | |||||
<PhoneNumberInput | |||||
border | |||||
/> | |||||
); | |||||
const border = screen.getByTestId('border'); | |||||
expect(border).toBeInTheDocument(); | |||||
}); | |||||
it('should render a label', () => { | |||||
render( | |||||
<PhoneNumberInput | |||||
label="foo" | |||||
/> | |||||
); | |||||
const textbox = screen.getByLabelText('foo'); | |||||
expect(textbox).toBeInTheDocument(); | |||||
const label = screen.getByTestId('label'); | |||||
expect(label).toHaveTextContent('foo'); | |||||
}); | |||||
it('should render a hidden label', () => { | |||||
render( | |||||
<PhoneNumberInput | |||||
label="foo" | |||||
hiddenLabel | |||||
/> | |||||
); | |||||
const textbox = screen.getByLabelText('foo'); | |||||
expect(textbox).toBeInTheDocument(); | |||||
const label = screen.queryByTestId('label'); | |||||
expect(label).toBeNull(); | |||||
}); | |||||
it('should render a hint', () => { | |||||
render( | |||||
<PhoneNumberInput | |||||
hint="foo" | |||||
/> | |||||
); | |||||
const hint = screen.getByTestId('hint'); | |||||
expect(hint).toBeInTheDocument(); | |||||
}); | |||||
it('should render an indicator', () => { | |||||
render( | |||||
<PhoneNumberInput | |||||
indicator={ | |||||
<div data-testid="indicator" /> | |||||
} | |||||
/> | |||||
); | |||||
const indicator = screen.getByTestId('indicator'); | |||||
expect(indicator).toBeInTheDocument(); | |||||
}); | |||||
describe.each([ | |||||
TextControlBase.TextControlSize.SMALL, | |||||
TextControlBase.TextControlSize.MEDIUM, | |||||
TextControlBase.TextControlSize.LARGE, | |||||
])('on %s size', (size) => { | |||||
it('should render input styles', () => { | |||||
render( | |||||
<PhoneNumberInput | |||||
size={size} | |||||
/> | |||||
); | |||||
expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render hint styles', () => { | |||||
render( | |||||
<PhoneNumberInput | |||||
size={size} | |||||
hint="hint" | |||||
/> | |||||
); | |||||
expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render indicator styles', () => { | |||||
render( | |||||
<PhoneNumberInput | |||||
size={size} | |||||
indicator={ | |||||
<div data-testid="indicator" /> | |||||
} | |||||
/> | |||||
); | |||||
expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
}); | |||||
it('should render a block textbox', () => { | |||||
render( | |||||
<PhoneNumberInput | |||||
block | |||||
/> | |||||
); | |||||
expect(TextControlBase.Root).toBeCalledWith(expect.objectContaining({ | |||||
block: true, | |||||
})); | |||||
}); | |||||
describe.each([ | |||||
TextControlBase.TextControlStyle.DEFAULT, | |||||
TextControlBase.TextControlStyle.ALTERNATE, | |||||
])('on %s style', (style) => { | |||||
it('should render input styles', () => { | |||||
render( | |||||
<PhoneNumberInput | |||||
style={style} | |||||
/> | |||||
); | |||||
expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
it('should render hint styles', () => { | |||||
render( | |||||
<PhoneNumberInput | |||||
style={style} | |||||
hint="hint" | |||||
/> | |||||
); | |||||
expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
it('should render indicator styles', () => { | |||||
render( | |||||
<PhoneNumberInput | |||||
style={style} | |||||
indicator={ | |||||
<div | |||||
data-testid="indicator" | |||||
/> | |||||
} | |||||
/> | |||||
); | |||||
expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
}); | |||||
it('should handle change events', () => { | |||||
const onChange = jest.fn(); | |||||
render( | |||||
<PhoneNumberInput | |||||
onChange={onChange} | |||||
/> | |||||
); | |||||
const textbox: HTMLInputElement = screen.getByRole('textbox'); | |||||
userEvent.type(textbox, 'foobar'); | |||||
expect(onChange).toBeCalled(); | |||||
}); | |||||
it('should handle input events', () => { | |||||
const onInput = jest.fn(); | |||||
render( | |||||
<PhoneNumberInput | |||||
onInput={onInput} | |||||
/> | |||||
); | |||||
const textbox: HTMLInputElement = screen.getByTestId('input'); | |||||
userEvent.type(textbox, 'foobar'); | |||||
expect(onInput).toBeCalled(); | |||||
}); | |||||
}); |
@@ -0,0 +1,124 @@ | |||||
import * as React from 'react'; | |||||
import * as TextControlBase from '@tesseract-design/web-base-textcontrol'; | |||||
export type PhoneNumberInputProps = Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'style'> & { | |||||
/** | |||||
* Short textual description indicating the nature of the component's value. | |||||
*/ | |||||
label?: React.ReactNode, | |||||
/** | |||||
* Short textual description as guidelines for valid input values. | |||||
*/ | |||||
hint?: React.ReactNode, | |||||
/** | |||||
* Size of the component. | |||||
*/ | |||||
size?: TextControlBase.TextControlSize, | |||||
/** | |||||
* Additional description, usually graphical, indicating the nature of the component's value. | |||||
*/ | |||||
indicator?: React.ReactNode, | |||||
/** | |||||
* Should the component display a border? | |||||
*/ | |||||
border?: boolean, | |||||
/** | |||||
* Should the component occupy the whole width of its parent? | |||||
*/ | |||||
block?: boolean, | |||||
/** | |||||
* Style of the component. | |||||
*/ | |||||
style?: TextControlBase.TextControlStyle, | |||||
/** | |||||
* Is the label hidden? | |||||
*/ | |||||
hiddenLabel?: boolean, | |||||
} | |||||
/** | |||||
* Component for inputting phone number values. | |||||
*/ | |||||
export const PhoneNumberInput = React.forwardRef<HTMLInputElement, PhoneNumberInputProps>( | |||||
( | |||||
{ | |||||
label = '', | |||||
hint = '', | |||||
indicator = null, | |||||
size = TextControlBase.TextControlSize.MEDIUM, | |||||
border = false, | |||||
block = false, | |||||
style = TextControlBase.TextControlStyle.DEFAULT, | |||||
hiddenLabel = false, | |||||
type: _type, | |||||
className: _className, | |||||
placeholder: _placeholder, | |||||
as: _as, | |||||
...etcProps | |||||
}: PhoneNumberInputProps, | |||||
ref, | |||||
) => { | |||||
const styleArgs = React.useMemo<TextControlBase.TextControlBaseArgs>(() => ({ | |||||
block, | |||||
border, | |||||
size, | |||||
indicator: Boolean(indicator), | |||||
style, | |||||
resizable: false, | |||||
predefinedValues: false, | |||||
}), [block, border, size, indicator, style]); | |||||
return ( | |||||
<div | |||||
className={TextControlBase.Root(styleArgs)} | |||||
> | |||||
<input | |||||
{...etcProps} | |||||
className={TextControlBase.Input(styleArgs)} | |||||
ref={ref} | |||||
aria-label={label} | |||||
type="tel" | |||||
data-testid="input" | |||||
/> | |||||
{ | |||||
border && ( | |||||
<span | |||||
data-testid="border" | |||||
/> | |||||
) | |||||
} | |||||
{ | |||||
label && !hiddenLabel && ( | |||||
<div | |||||
data-testid="label" | |||||
className={TextControlBase.LabelWrapper(styleArgs)} | |||||
> | |||||
{label} | |||||
</div> | |||||
) | |||||
} | |||||
{hint && ( | |||||
<div | |||||
className={TextControlBase.HintWrapper(styleArgs)} | |||||
data-testid="hint" | |||||
> | |||||
<div | |||||
className={TextControlBase.Hint()} | |||||
> | |||||
{hint} | |||||
</div> | |||||
</div> | |||||
)} | |||||
{indicator && ( | |||||
<div | |||||
className={TextControlBase.IndicatorWrapper(styleArgs)} | |||||
> | |||||
{indicator} | |||||
</div> | |||||
)} | |||||
</div> | |||||
); | |||||
} | |||||
); | |||||
PhoneNumberInput.displayName = 'PhoneNumberInput'; |
@@ -0,0 +1,210 @@ | |||||
import * as React from 'react'; | |||||
import { | |||||
render, | |||||
screen | |||||
} from '@testing-library/react'; | |||||
import '@testing-library/jest-dom'; | |||||
import userEvent from '@testing-library/user-event'; | |||||
import * as TextControlBase from '@tesseract-design/web-base-textcontrol'; | |||||
import { | |||||
UrlInput, | |||||
} from '.'; | |||||
jest.mock('@tesseract-design/web-base-textcontrol'); | |||||
describe('UrlInput', () => { | |||||
it('should render a textbox', () => { | |||||
render( | |||||
<UrlInput /> | |||||
); | |||||
const textbox = screen.getByRole('textbox'); | |||||
expect(textbox).toBeInTheDocument(); | |||||
expect(textbox).toHaveProperty('type', 'url'); | |||||
}); | |||||
it('should render a border', () => { | |||||
render( | |||||
<UrlInput | |||||
border | |||||
/> | |||||
); | |||||
const border = screen.getByTestId('border'); | |||||
expect(border).toBeInTheDocument(); | |||||
}); | |||||
it('should render a label', () => { | |||||
render( | |||||
<UrlInput | |||||
label="foo" | |||||
/> | |||||
); | |||||
const textbox = screen.getByLabelText('foo'); | |||||
expect(textbox).toBeInTheDocument(); | |||||
const label = screen.getByTestId('label'); | |||||
expect(label).toHaveTextContent('foo'); | |||||
}); | |||||
it('should render a hidden label', () => { | |||||
render( | |||||
<UrlInput | |||||
label="foo" | |||||
hiddenLabel | |||||
/> | |||||
); | |||||
const textbox = screen.getByLabelText('foo'); | |||||
expect(textbox).toBeInTheDocument(); | |||||
const label = screen.queryByTestId('label'); | |||||
expect(label).toBeNull(); | |||||
}); | |||||
it('should render a hint', () => { | |||||
render( | |||||
<UrlInput | |||||
hint="foo" | |||||
/> | |||||
); | |||||
const hint = screen.getByTestId('hint'); | |||||
expect(hint).toBeInTheDocument(); | |||||
}); | |||||
it('should render an indicator', () => { | |||||
render( | |||||
<UrlInput | |||||
indicator={ | |||||
<div data-testid="indicator" /> | |||||
} | |||||
/> | |||||
); | |||||
const indicator = screen.getByTestId('indicator'); | |||||
expect(indicator).toBeInTheDocument(); | |||||
}); | |||||
describe.each([ | |||||
TextControlBase.TextControlSize.SMALL, | |||||
TextControlBase.TextControlSize.MEDIUM, | |||||
TextControlBase.TextControlSize.LARGE, | |||||
])('on %s size', (size) => { | |||||
it('should render input styles', () => { | |||||
render( | |||||
<UrlInput | |||||
size={size} | |||||
/> | |||||
); | |||||
expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render hint styles', () => { | |||||
render( | |||||
<UrlInput | |||||
size={size} | |||||
hint="hint" | |||||
/> | |||||
); | |||||
expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render indicator styles', () => { | |||||
render( | |||||
<UrlInput | |||||
size={size} | |||||
indicator={ | |||||
<div data-testid="indicator" /> | |||||
} | |||||
/> | |||||
); | |||||
expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
}); | |||||
it('should render a block textbox', () => { | |||||
render( | |||||
<UrlInput | |||||
block | |||||
/> | |||||
); | |||||
expect(TextControlBase.Root).toBeCalledWith(expect.objectContaining({ | |||||
block: true, | |||||
})); | |||||
}); | |||||
describe.each([ | |||||
TextControlBase.TextControlStyle.DEFAULT, | |||||
TextControlBase.TextControlStyle.ALTERNATE, | |||||
])('on %s style', (style) => { | |||||
it('should render input styles', () => { | |||||
render( | |||||
<UrlInput | |||||
style={style} | |||||
/> | |||||
); | |||||
expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
it('should render hint styles', () => { | |||||
render( | |||||
<UrlInput | |||||
style={style} | |||||
hint="hint" | |||||
/> | |||||
); | |||||
expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
it('should render indicator styles', () => { | |||||
render( | |||||
<UrlInput | |||||
style={style} | |||||
indicator={ | |||||
<div | |||||
data-testid="indicator" | |||||
/> | |||||
} | |||||
/> | |||||
); | |||||
expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
}); | |||||
it('should handle change events', () => { | |||||
const onChange = jest.fn(); | |||||
render( | |||||
<UrlInput | |||||
onChange={onChange} | |||||
/> | |||||
); | |||||
const textbox: HTMLInputElement = screen.getByRole('textbox'); | |||||
userEvent.type(textbox, 'foobar'); | |||||
expect(onChange).toBeCalled(); | |||||
}); | |||||
it('should handle input events', () => { | |||||
const onInput = jest.fn(); | |||||
render( | |||||
<UrlInput | |||||
onInput={onInput} | |||||
/> | |||||
); | |||||
const textbox: HTMLInputElement = screen.getByTestId('input'); | |||||
userEvent.type(textbox, 'foobar'); | |||||
expect(onInput).toBeCalled(); | |||||
}); | |||||
}); |
@@ -0,0 +1,124 @@ | |||||
import * as React from 'react'; | |||||
import * as TextControlBase from '@tesseract-design/web-base-textcontrol'; | |||||
export type UrlInputProps = Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'style'> & { | |||||
/** | |||||
* Short textual description indicating the nature of the component's value. | |||||
*/ | |||||
label?: React.ReactNode, | |||||
/** | |||||
* Short textual description as guidelines for valid input values. | |||||
*/ | |||||
hint?: React.ReactNode, | |||||
/** | |||||
* Size of the component. | |||||
*/ | |||||
size?: TextControlBase.TextControlSize, | |||||
/** | |||||
* Additional description, usually graphical, indicating the nature of the component's value. | |||||
*/ | |||||
indicator?: React.ReactNode, | |||||
/** | |||||
* Should the component display a border? | |||||
*/ | |||||
border?: boolean, | |||||
/** | |||||
* Should the component occupy the whole width of its parent? | |||||
*/ | |||||
block?: boolean, | |||||
/** | |||||
* Style of the component. | |||||
*/ | |||||
style?: TextControlBase.TextControlStyle, | |||||
/** | |||||
* Is the label hidden? | |||||
*/ | |||||
hiddenLabel?: boolean, | |||||
} | |||||
/** | |||||
* Component for inputting URL values. | |||||
*/ | |||||
export const UrlInput = React.forwardRef<HTMLInputElement, UrlInputProps>( | |||||
( | |||||
{ | |||||
label = '', | |||||
hint = '', | |||||
indicator = null, | |||||
size = TextControlBase.TextControlSize.MEDIUM, | |||||
border = false, | |||||
block = false, | |||||
style = TextControlBase.TextControlStyle.DEFAULT, | |||||
hiddenLabel = false, | |||||
type: _type, | |||||
className: _className, | |||||
placeholder: _placeholder, | |||||
as: _as, | |||||
...etcProps | |||||
}: UrlInputProps, | |||||
ref, | |||||
) => { | |||||
const styleArgs = React.useMemo<TextControlBase.TextControlBaseArgs>(() => ({ | |||||
block, | |||||
border, | |||||
size, | |||||
indicator: Boolean(indicator), | |||||
style, | |||||
resizable: false, | |||||
predefinedValues: false, | |||||
}), [block, border, size, indicator, style]); | |||||
return ( | |||||
<div | |||||
className={TextControlBase.Root(styleArgs)} | |||||
> | |||||
<input | |||||
{...etcProps} | |||||
className={TextControlBase.Input(styleArgs)} | |||||
ref={ref} | |||||
aria-label={label} | |||||
type="url" | |||||
data-testid="input" | |||||
/> | |||||
{ | |||||
border && ( | |||||
<span | |||||
data-testid="border" | |||||
/> | |||||
) | |||||
} | |||||
{ | |||||
label && !hiddenLabel && ( | |||||
<div | |||||
data-testid="label" | |||||
className={TextControlBase.LabelWrapper(styleArgs)} | |||||
> | |||||
{label} | |||||
</div> | |||||
) | |||||
} | |||||
{hint && ( | |||||
<div | |||||
className={TextControlBase.HintWrapper(styleArgs)} | |||||
data-testid="hint" | |||||
> | |||||
<div | |||||
className={TextControlBase.Hint()} | |||||
> | |||||
{hint} | |||||
</div> | |||||
</div> | |||||
)} | |||||
{indicator && ( | |||||
<div | |||||
className={TextControlBase.IndicatorWrapper(styleArgs)} | |||||
> | |||||
{indicator} | |||||
</div> | |||||
)} | |||||
</div> | |||||
); | |||||
} | |||||
); | |||||
UrlInput.displayName = 'UrlInput'; |
@@ -0,0 +1,3 @@ | |||||
export * from './components/EmailAddressInput'; | |||||
export * from './components/PhoneNumberInput'; | |||||
export * from './components/UrlInput'; |
@@ -0,0 +1,11 @@ | |||||
import * as WebFormattedReact from '.'; | |||||
describe('web-formatted-react', () => { | |||||
it.each([ | |||||
'EmailAddressInput', | |||||
'PhoneNumberInput', | |||||
'UrlInput', | |||||
])('should export %s', (namedExport) => { | |||||
expect(WebFormattedReact).toHaveProperty(namedExport); | |||||
}); | |||||
}); |
@@ -66,6 +66,7 @@ export const RadioButton = React.forwardRef<HTMLInputElement, RadioButtonProps>( | |||||
disabled, | disabled, | ||||
appearance: CheckControlBase.CheckControlAppearance.BUTTON, | appearance: CheckControlBase.CheckControlAppearance.BUTTON, | ||||
type: 'radio', | type: 'radio', | ||||
uncheckedLabel: false, | |||||
}), [size, block, variant, border, compact, disabled]); | }), [size, block, variant, border, compact, disabled]); | ||||
return ( | return ( | ||||
@@ -39,6 +39,7 @@ export const RadioTickBox = React.forwardRef<HTMLInputElement, RadioTickBoxProps | |||||
compact, | compact, | ||||
appearance: CheckControlBase.CheckControlAppearance.TICK_BOX, | appearance: CheckControlBase.CheckControlAppearance.TICK_BOX, | ||||
type: 'radio', | type: 'radio', | ||||
uncheckedLabel: false, | |||||
}), [block, compact]); | }), [block, compact]); | ||||
return ( | return ( | ||||
@@ -71,6 +71,7 @@ export const ToggleButton = React.forwardRef<HTMLInputElement, ToggleButtonProps | |||||
disabled, | disabled, | ||||
appearance: CheckControlBase.CheckControlAppearance.BUTTON, | appearance: CheckControlBase.CheckControlAppearance.BUTTON, | ||||
type: 'checkbox', | type: 'checkbox', | ||||
uncheckedLabel: false, | |||||
}), [size, block, variant, border, compact, disabled]); | }), [size, block, variant, border, compact, disabled]); | ||||
const defaultRef = React.useRef<HTMLInputElement>(null); | const defaultRef = React.useRef<HTMLInputElement>(null); | ||||
const theRef = (ref ?? defaultRef) as React.MutableRefObject<HTMLInputElement>; | const theRef = (ref ?? defaultRef) as React.MutableRefObject<HTMLInputElement>; | ||||
@@ -44,6 +44,7 @@ export const ToggleTickBox = React.forwardRef<HTMLInputElement, ToggleTickBoxPro | |||||
compact, | compact, | ||||
appearance: CheckControlBase.CheckControlAppearance.TICK_BOX, | appearance: CheckControlBase.CheckControlAppearance.TICK_BOX, | ||||
type: 'checkbox', | type: 'checkbox', | ||||
uncheckedLabel: false, | |||||
}), [block, compact]); | }), [block, compact]); | ||||
const defaultRef = React.useRef<HTMLInputElement>(null); | const defaultRef = React.useRef<HTMLInputElement>(null); | ||||
const theRef = (ref ?? defaultRef) as React.MutableRefObject<HTMLInputElement>; | const theRef = (ref ?? defaultRef) as React.MutableRefObject<HTMLInputElement>; | ||||
@@ -15,7 +15,9 @@ | |||||
"jsx": "preserve", | "jsx": "preserve", | ||||
"baseUrl": ".", | "baseUrl": ".", | ||||
"paths": { | "paths": { | ||||
"@tesseract-design/css-utils": ["./src/modules/css-utils"], | |||||
"@tesseract-design/web-action-react": ["./src/modules/action"], | "@tesseract-design/web-action-react": ["./src/modules/action"], | ||||
"@tesseract-design/web-formatted-react": ["./src/modules/formatted"], | |||||
"@tesseract-design/web-freeform-react": ["./src/modules/freeform"], | "@tesseract-design/web-freeform-react": ["./src/modules/freeform"], | ||||
"@tesseract-design/web-information-react": ["./src/modules/information"], | "@tesseract-design/web-information-react": ["./src/modules/information"], | ||||
"@tesseract-design/web-navigation-react": ["./src/modules/navigation"], | "@tesseract-design/web-navigation-react": ["./src/modules/navigation"], | ||||
@@ -24,8 +26,7 @@ | |||||
"@tesseract-design/web-base-button": ["./src/modules/base-button"], | "@tesseract-design/web-base-button": ["./src/modules/base-button"], | ||||
"@tesseract-design/web-base-checkcontrol": ["./src/modules/base-checkcontrol"], | "@tesseract-design/web-base-checkcontrol": ["./src/modules/base-checkcontrol"], | ||||
"@tesseract-design/web-base-selectcontrol": ["./src/modules/base-selectcontrol"], | "@tesseract-design/web-base-selectcontrol": ["./src/modules/base-selectcontrol"], | ||||
"@tesseract-design/web-base-textcontrol": ["./src/modules/base-textcontrol"], | |||||
"@tesseract-design/css-utils": ["./src/modules/css-utils"] | |||||
"@tesseract-design/web-base-textcontrol": ["./src/modules/base-textcontrol"] | |||||
} | } | ||||
}, | }, | ||||
"include": [ | "include": [ | ||||