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` | |||
# Code | |||
- [ ] `CodeBlock` | |||
- [ ] `CodeInput` | |||
- [ ] `VerificationCodeInput` | |||
# Color | |||
- [ ] `ColorSlider` | |||
- [ ] `Palette` | |||
- [ ] `Swatch` | |||
# DataGrid | |||
- [ ] `DataGridView` | |||
# File | |||
- [ ] `FileArea` | |||
@@ -15,10 +25,10 @@ | |||
# Formatted | |||
- [X] `EmailInput` | |||
- [ ] `SuggestionInput` | |||
- [ ] `EmailInput` | |||
- [ ] `TelInput` | |||
- [ ] `UrlInput` | |||
- [X] `TelInput` | |||
- [X] `UrlInput` | |||
# Freeform | |||
@@ -34,6 +44,10 @@ | |||
- [X] `Badge` | |||
# MIDI | |||
- [ ] `MusicalKeyboard` | |||
# Navigation | |||
- [ ] `Breadcrumb` | |||
@@ -59,6 +73,14 @@ | |||
- [X] `ToggleSwitch` | |||
- [X] `ToggleTickBox` | |||
# Rating | |||
- [ ] `Rating` | |||
# RichText | |||
- [ ] `WysiwygTextInput` | |||
# Temporal | |||
- [ ] `Calendar` | |||
@@ -68,3 +90,7 @@ | |||
- [ ] `MonthDaySpinner` | |||
- [ ] `TimeSpinner` | |||
- [ ] `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, | |||
appearance: CheckControlBase.CheckControlAppearance.BUTTON, | |||
type: 'radio', | |||
uncheckedLabel: false, | |||
}), [size, block, variant, border, compact, disabled]); | |||
return ( | |||
@@ -39,6 +39,7 @@ export const RadioTickBox = React.forwardRef<HTMLInputElement, RadioTickBoxProps | |||
compact, | |||
appearance: CheckControlBase.CheckControlAppearance.TICK_BOX, | |||
type: 'radio', | |||
uncheckedLabel: false, | |||
}), [block, compact]); | |||
return ( | |||
@@ -71,6 +71,7 @@ export const ToggleButton = React.forwardRef<HTMLInputElement, ToggleButtonProps | |||
disabled, | |||
appearance: CheckControlBase.CheckControlAppearance.BUTTON, | |||
type: 'checkbox', | |||
uncheckedLabel: false, | |||
}), [size, block, variant, border, compact, disabled]); | |||
const defaultRef = React.useRef<HTMLInputElement>(null); | |||
const theRef = (ref ?? defaultRef) as React.MutableRefObject<HTMLInputElement>; | |||
@@ -44,6 +44,7 @@ export const ToggleTickBox = React.forwardRef<HTMLInputElement, ToggleTickBoxPro | |||
compact, | |||
appearance: CheckControlBase.CheckControlAppearance.TICK_BOX, | |||
type: 'checkbox', | |||
uncheckedLabel: false, | |||
}), [block, compact]); | |||
const defaultRef = React.useRef<HTMLInputElement>(null); | |||
const theRef = (ref ?? defaultRef) as React.MutableRefObject<HTMLInputElement>; | |||
@@ -15,7 +15,9 @@ | |||
"jsx": "preserve", | |||
"baseUrl": ".", | |||
"paths": { | |||
"@tesseract-design/css-utils": ["./src/modules/css-utils"], | |||
"@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-information-react": ["./src/modules/information"], | |||
"@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-checkcontrol": ["./src/modules/base-checkcontrol"], | |||
"@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": [ | |||