Implement tests to increase code coverage The option module was modified to split components and avoid overloading.master
@@ -34,4 +34,5 @@ yarn-error.log* | |||||
# vercel | # vercel | ||||
.vercel | .vercel | ||||
.idea/ | |||||
.idea/ | |||||
coverage/ |
@@ -0,0 +1,45 @@ | |||||
# Tesseract Web | |||||
## Rationale | |||||
- Every component library makes their own conventions of component organization | |||||
- Graceful degradation through backwards compatibility with HTML controls is not the main focus | |||||
- In return, aspects such as accessibility may suffer since HTML controls are generally compliant to accessibility | |||||
considerations suggested by approved standards | |||||
- Emerging Web applications are at risk of deviating through said standards which can fragment the Web platform | |||||
implementation | |||||
- Hopefully we can inspire component development through the component organization we are proposing; | |||||
everyone can have their own implementations | |||||
- cf. Authoring (publishing interfaces) vs Dependency (coercing consumers to adapt to concrete implementations) | |||||
## Ground Rules | |||||
- Each component is an enhanced version of an HTML control (see [HTML enhancement](#html-enhancement)) | |||||
- Each component should only do one thing and one thing only in terms of purpose and appearance (contrast to HTML's | |||||
`<input>` where its function is overloaded depending on its `type` attribute) | |||||
- Each component has decoupled styling, wherein each styling has their own API | |||||
- This styling API is then made cross-framework compatible | |||||
- This styling API allows use of dynamic styles (which then requires CSS-in-JS way of application) | |||||
## HTML enhancement | |||||
| HTML element | Tesseract counterpart | Remarks | | |||||
|----------------------------------------------------------------------------|-------------------------------|-----------------------------------------------------------------------| | |||||
| `<button>` | `action/ActionButton` | | | |||||
| `<input type="button">` | `action/ActionButton` | | | |||||
| `<input type="reset">` | `action/ActionButton` | | | |||||
| `<input type="submit">` | `action/ActionButton` | | | |||||
| `<textarea>` | `freeform/MultilineTextInput` | | | |||||
| `<input type="text">` | `freeform/TextInput` | | | |||||
| `<input type="search">` | `freeform/TextInput` | | | |||||
| `<input type="password">` | `freeform/MaskedTextInput` | | | |||||
| `<a>` with button appearance | `navigation/LinkButton` | | | |||||
| `<select>` without `multiple` attribute | `option/DropdownSelect` | | | |||||
| `<input type="checkbox">` with `indeterminate` state | `option/ToggleTickBox` | | | |||||
| `<input type="checkbox">` with `indeterminate` state and button appearance | `option/ToggleButton` | | | |||||
| `<input type="checkbox">` without `indeterminate` state | `option/ToggleSwitch` | Prefer using this component when indeterminate state is not expected. | | |||||
| `<input type="radio">` | `option/RadioTickBox` | | | |||||
| `<input type="radio">` with button appearance | `option/RadioButton` | | | |||||
| `<input type="number">` | `number/Spinner` | Use this component for discrete values. | | |||||
| `<input type="range">` | `number/Slider` | Use this component for continuous values. | | |||||
@@ -1,3 +1,5 @@ | |||||
const tsconfig = require('./tsconfig.json'); | |||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ | ||||
module.exports = { | module.exports = { | ||||
preset: 'ts-jest', | preset: 'ts-jest', | ||||
@@ -7,4 +9,13 @@ module.exports = { | |||||
tsconfig: 'tsconfig.test.json', | tsconfig: 'tsconfig.test.json', | ||||
}, | }, | ||||
}, | }, | ||||
moduleNameMapper: Object.fromEntries( | |||||
Object | |||||
.entries(tsconfig.compilerOptions.paths) | |||||
.map(([alias, paths]) => [alias, paths[0].replace('.', '<rootDir>')]) | |||||
), | |||||
collectCoverageFrom: [ | |||||
'src/modules/**/*.{ts,tsx}', | |||||
'!src/modules/base-*/**/*.*', | |||||
], | |||||
}; | }; |
@@ -6,7 +6,8 @@ | |||||
"dev": "next dev", | "dev": "next dev", | ||||
"build": "next build", | "build": "next build", | ||||
"start": "next start", | "start": "next start", | ||||
"lint": "next lint" | |||||
"lint": "next lint", | |||||
"test": "jest" | |||||
}, | }, | ||||
"dependencies": { | "dependencies": { | ||||
"goober": "^2.0.41", | "goober": "^2.0.41", | ||||
@@ -16,6 +17,7 @@ | |||||
"tailwindcss": "^2.2.16" | "tailwindcss": "^2.2.16" | ||||
}, | }, | ||||
"devDependencies": { | "devDependencies": { | ||||
"@testing-library/user-event": "^13.5.0", | |||||
"@types/jest": "^27.0.3", | "@types/jest": "^27.0.3", | ||||
"@types/react": "17.0.27", | "@types/react": "17.0.27", | ||||
"eslint": "^7.32.0", | "eslint": "^7.32.0", | ||||
@@ -0,0 +1,201 @@ | |||||
import * as React from 'react'; | |||||
import { | |||||
render, | |||||
screen, | |||||
} from '@testing-library/react'; | |||||
import '@testing-library/jest-dom'; | |||||
import { | |||||
ActionButton, | |||||
ActionButtonType, | |||||
} from '.'; | |||||
import userEvent from '@testing-library/user-event'; | |||||
import * as ButtonBase from '@tesseract-design/web-base-button'; | |||||
jest.mock('@tesseract-design/web-base-button'); | |||||
describe('ActionButton', () => { | |||||
it('should render a button', () => { | |||||
render( | |||||
<ActionButton /> | |||||
); | |||||
const button: HTMLButtonElement = screen.getByRole('button'); | |||||
expect(button).toBeInTheDocument(); | |||||
expect(button).toHaveProperty('type', 'button'); | |||||
}); | |||||
it('should render a subtext', () => { | |||||
render( | |||||
<ActionButton | |||||
subtext="subtext" | |||||
/> | |||||
); | |||||
const subtext: HTMLElement = screen.getByTestId('subtext'); | |||||
expect(subtext).toBeInTheDocument(); | |||||
}); | |||||
it('should render a badge', () => { | |||||
render( | |||||
<ActionButton | |||||
badge="badge" | |||||
/> | |||||
); | |||||
const badge: HTMLElement = screen.getByTestId('badge'); | |||||
expect(badge).toBeInTheDocument(); | |||||
}); | |||||
it('should render as a menu item', () => { | |||||
render( | |||||
<ActionButton | |||||
menuItem | |||||
/> | |||||
); | |||||
const menuItemIndicator: HTMLElement = screen.getByTestId('menuItemIndicator'); | |||||
expect(menuItemIndicator).toBeInTheDocument(); | |||||
}); | |||||
it('should handle click events', () => { | |||||
const onClick = jest.fn(); | |||||
render( | |||||
<ActionButton | |||||
onClick={onClick} | |||||
/> | |||||
); | |||||
const button: HTMLButtonElement = screen.getByRole('button'); | |||||
userEvent.click(button); | |||||
expect(onClick).toBeCalled(); | |||||
}); | |||||
it('should render a compact button', () => { | |||||
render( | |||||
<ActionButton | |||||
compact | |||||
/> | |||||
); | |||||
expect(ButtonBase.Button).toBeCalledWith(expect.objectContaining({ | |||||
compact: true, | |||||
})); | |||||
expect(ButtonBase.Label).toBeCalledWith(expect.objectContaining({ | |||||
compact: true, | |||||
})); | |||||
}); | |||||
describe.each([ | |||||
ButtonBase.ButtonSize.SMALL, | |||||
ButtonBase.ButtonSize.MEDIUM, | |||||
ButtonBase.ButtonSize.LARGE, | |||||
])('on %s size', (size) => { | |||||
it('should render button styles', () => { | |||||
render( | |||||
<ActionButton | |||||
size={size} | |||||
/> | |||||
); | |||||
expect(ButtonBase.Button).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render badge styles', () => { | |||||
render( | |||||
<ActionButton | |||||
size={size} | |||||
badge="badge" | |||||
/> | |||||
); | |||||
expect(ButtonBase.BadgeContainer).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render indicator styles', () => { | |||||
render( | |||||
<ActionButton | |||||
size={size} | |||||
menuItem | |||||
/> | |||||
); | |||||
expect(ButtonBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
}); | |||||
it.each([ | |||||
ButtonBase.ButtonVariant.OUTLINE, | |||||
ButtonBase.ButtonVariant.FILLED, | |||||
])('should render a button with variant %s', (variant) => { | |||||
render( | |||||
<ActionButton | |||||
variant={variant} | |||||
/> | |||||
); | |||||
expect(ButtonBase.Button).toBeCalledWith(expect.objectContaining({ | |||||
variant, | |||||
})); | |||||
}); | |||||
it('should render a bordered button', () => { | |||||
render( | |||||
<ActionButton | |||||
border | |||||
/> | |||||
); | |||||
expect(ButtonBase.Border).toBeCalledWith(expect.objectContaining({ | |||||
border: true, | |||||
})); | |||||
}); | |||||
it('should render a block button', () => { | |||||
render( | |||||
<ActionButton | |||||
block | |||||
/> | |||||
); | |||||
expect(ButtonBase.Border).toBeCalledWith(expect.objectContaining({ | |||||
block: true, | |||||
})); | |||||
}); | |||||
it('should render children', () => { | |||||
render( | |||||
<ActionButton> | |||||
Foo | |||||
</ActionButton> | |||||
); | |||||
const children: HTMLElement = screen.getByTestId('children'); | |||||
expect(children).toHaveTextContent('Foo'); | |||||
}); | |||||
it.each([ | |||||
ActionButtonType.BUTTON, | |||||
ActionButtonType.RESET, | |||||
ActionButtonType.SUBMIT, | |||||
])('should render a button with type %s', (buttonType) => { | |||||
render( | |||||
<ActionButton | |||||
type={buttonType} | |||||
/> | |||||
); | |||||
const button: HTMLButtonElement = screen.getByRole('button'); | |||||
expect(button).toHaveProperty('type', buttonType); | |||||
}); | |||||
it('should render a disabled button', () => { | |||||
render( | |||||
<ActionButton | |||||
disabled | |||||
/> | |||||
); | |||||
const button: HTMLButtonElement = screen.getByRole('button'); | |||||
expect(button).toBeDisabled(); | |||||
}); | |||||
}); |
@@ -1,12 +1,18 @@ | |||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import * as ButtonBase from '@tesseract-design/web-base-button'; | import * as ButtonBase from '@tesseract-design/web-base-button'; | ||||
/** | |||||
* Available ActionButton type values. | |||||
*/ | |||||
export enum ActionButtonType { | export enum ActionButtonType { | ||||
SUBMIT = 'submit', | SUBMIT = 'submit', | ||||
RESET = 'reset', | RESET = 'reset', | ||||
BUTTON = 'button', | BUTTON = 'button', | ||||
} | } | ||||
/** | |||||
* Props for the component. | |||||
*/ | |||||
export type ActionButtonProps = Omit<React.HTMLProps<HTMLButtonElement>, 'size' | 'type' | 'style'> & { | export type ActionButtonProps = Omit<React.HTMLProps<HTMLButtonElement>, 'size' | 'type' | 'style'> & { | ||||
/** | /** | ||||
* Size of the component. | * Size of the component. | ||||
@@ -28,10 +34,6 @@ export type ActionButtonProps = Omit<React.HTMLProps<HTMLButtonElement>, 'size' | |||||
* Type of the component. | * Type of the component. | ||||
*/ | */ | ||||
type?: ActionButtonType, | type?: ActionButtonType, | ||||
/** | |||||
* Style of the component. | |||||
*/ | |||||
style?: ButtonBase.ButtonStyle, | |||||
/** | /** | ||||
* Does the component need to conserve space? | * Does the component need to conserve space? | ||||
*/ | */ | ||||
@@ -64,7 +66,6 @@ export const ActionButton = React.forwardRef<HTMLButtonElement, ActionButtonProp | |||||
children, | children, | ||||
type = ActionButtonType.BUTTON, | type = ActionButtonType.BUTTON, | ||||
block = false, | block = false, | ||||
style = ButtonBase.ButtonStyle.DEFAULT, | |||||
disabled = false, | disabled = false, | ||||
compact = false, | compact = false, | ||||
subtext, | subtext, | ||||
@@ -81,11 +82,10 @@ export const ActionButton = React.forwardRef<HTMLButtonElement, ActionButtonProp | |||||
block, | block, | ||||
variant, | variant, | ||||
border, | border, | ||||
style, | |||||
compact, | compact, | ||||
menuItem, | menuItem, | ||||
disabled, | disabled, | ||||
}), [size, block, variant, border, style, compact, menuItem]); | |||||
}), [size, block, variant, border, compact, menuItem, disabled]); | |||||
return ( | return ( | ||||
<button | <button | ||||
@@ -93,7 +93,7 @@ export const ActionButton = React.forwardRef<HTMLButtonElement, ActionButtonProp | |||||
disabled={disabled} | disabled={disabled} | ||||
className={ButtonBase.Button(styleProps)} | className={ButtonBase.Button(styleProps)} | ||||
ref={ref} | ref={ref} | ||||
type={type ?? ActionButtonType.BUTTON} | |||||
type={type} | |||||
> | > | ||||
<span | <span | ||||
className={ButtonBase.Border(styleProps)} | className={ButtonBase.Border(styleProps)} | ||||
@@ -103,6 +103,7 @@ export const ActionButton = React.forwardRef<HTMLButtonElement, ActionButtonProp | |||||
> | > | ||||
<span | <span | ||||
className={ButtonBase.MainText()} | className={ButtonBase.MainText()} | ||||
data-testid="children" | |||||
> | > | ||||
<span | <span | ||||
className={ButtonBase.OverflowText()} | className={ButtonBase.OverflowText()} | ||||
@@ -115,7 +116,10 @@ export const ActionButton = React.forwardRef<HTMLButtonElement, ActionButtonProp | |||||
&& ( | && ( | ||||
<> | <> | ||||
{' '} | {' '} | ||||
<span className={ButtonBase.Subtext()}> | |||||
<span | |||||
className={ButtonBase.Subtext()} | |||||
data-testid="subtext" | |||||
> | |||||
<span | <span | ||||
className={ButtonBase.OverflowText()} | className={ButtonBase.OverflowText()} | ||||
> | > | ||||
@@ -133,6 +137,7 @@ export const ActionButton = React.forwardRef<HTMLButtonElement, ActionButtonProp | |||||
{' '} | {' '} | ||||
<span | <span | ||||
className={ButtonBase.BadgeContainer(styleProps)} | className={ButtonBase.BadgeContainer(styleProps)} | ||||
data-testid="badge" | |||||
> | > | ||||
{badge} | {badge} | ||||
</span> | </span> | ||||
@@ -146,6 +151,7 @@ export const ActionButton = React.forwardRef<HTMLButtonElement, ActionButtonProp | |||||
{' '} | {' '} | ||||
<span | <span | ||||
className={ButtonBase.IndicatorWrapper(styleProps)} | className={ButtonBase.IndicatorWrapper(styleProps)} | ||||
data-testid="menuItemIndicator" | |||||
> | > | ||||
<svg | <svg | ||||
className={ButtonBase.Indicator()} | className={ButtonBase.Indicator()} | ||||
@@ -0,0 +1,9 @@ | |||||
import * as WebActionReact from '.'; | |||||
describe('web-action-react', () => { | |||||
it.each([ | |||||
'ActionButton', | |||||
])('should export %s', (namedExport) => { | |||||
expect(WebActionReact).toHaveProperty(namedExport); | |||||
}); | |||||
}); |
@@ -0,0 +1,42 @@ | |||||
import { css } from '@tesseract-design/css-utils'; | |||||
export type BadgeBaseArgs = { | |||||
rounded: boolean, | |||||
} | |||||
export const Root = ({ | |||||
rounded, | |||||
}: BadgeBaseArgs) => css.cx( | |||||
css` | |||||
position: relative; | |||||
height: 1.5em; | |||||
min-width: 1.5em; | |||||
display: inline-grid; | |||||
vertical-align: middle; | |||||
place-content: center; | |||||
overflow: hidden; | |||||
font-stretch: var(--font-stretch-base, normal); | |||||
padding: 0 0.25rem; | |||||
box-sizing: border-box; | |||||
&::before { | |||||
position: absolute; | |||||
top: 0; | |||||
left: 0; | |||||
width: 100%; | |||||
height: 100%; | |||||
background-color: currentColor; | |||||
opacity: 0.25; | |||||
content: ''; | |||||
} | |||||
`, | |||||
css.dynamic({ | |||||
'border-radius': rounded ? '0.75em' : '0.25rem', | |||||
}), | |||||
); | |||||
export const Content = () => css.cx( | |||||
css` | |||||
position: relative; | |||||
font-size: 0.75em; | |||||
` | |||||
); |
@@ -11,17 +11,12 @@ export enum ButtonVariant { | |||||
FILLED = 'filled', | FILLED = 'filled', | ||||
} | } | ||||
export enum ButtonStyle { | |||||
DEFAULT = 'default', | |||||
} | |||||
export type ButtonBaseArgs = { | export type ButtonBaseArgs = { | ||||
size: ButtonSize, | size: ButtonSize, | ||||
block: boolean, | block: boolean, | ||||
variant: ButtonVariant, | variant: ButtonVariant, | ||||
border: boolean, | border: boolean, | ||||
disabled: boolean, | disabled: boolean, | ||||
style: ButtonStyle, | |||||
compact: boolean, | compact: boolean, | ||||
menuItem: boolean, | menuItem: boolean, | ||||
} | } | ||||
@@ -186,7 +181,6 @@ export const Border = ({ | |||||
), | ), | ||||
); | ); | ||||
export const Label = ({ | export const Label = ({ | ||||
compact, | compact, | ||||
menuItem, | menuItem, | ||||
@@ -1,7 +1,7 @@ | |||||
import { css } from '@tesseract-design/css-utils' | import { css } from '@tesseract-design/css-utils' | ||||
export enum CheckControlAppearance { | export enum CheckControlAppearance { | ||||
DEFAULT = 'default', | |||||
TICK_BOX = 'tick-box', | |||||
BUTTON = 'button', | BUTTON = 'button', | ||||
SWITCH = 'switch', | SWITCH = 'switch', | ||||
} | } | ||||
@@ -62,7 +62,7 @@ export const CheckStateContainer = ({ | |||||
`, | `, | ||||
css.nest('&:checked + * > :first-child + * > *') ( | css.nest('&:checked + * > :first-child + * > *') ( | ||||
css.if ( | css.if ( | ||||
appearance === CheckControlAppearance.DEFAULT | |||||
appearance === CheckControlAppearance.TICK_BOX | |||||
|| appearance === CheckControlAppearance.BUTTON | || appearance === CheckControlAppearance.BUTTON | ||||
) ( | ) ( | ||||
css.if (type === 'checkbox') ( | css.if (type === 'checkbox') ( | ||||
@@ -106,7 +106,7 @@ export const CheckStateContainer = ({ | |||||
css.nest('&:indeterminate[type="checkbox"] + * > :first-child + * > *') ( | css.nest('&:indeterminate[type="checkbox"] + * > :first-child + * > *') ( | ||||
css.if ( | css.if ( | ||||
appearance === CheckControlAppearance.BUTTON | appearance === CheckControlAppearance.BUTTON | ||||
|| appearance === CheckControlAppearance.DEFAULT | |||||
|| appearance === CheckControlAppearance.TICK_BOX | |||||
) ( | ) ( | ||||
css` | css` | ||||
width: 1.5em; | width: 1.5em; | ||||
@@ -125,7 +125,7 @@ export const CheckStateContainer = ({ | |||||
css.nest('&:indeterminate[type="checkbox"] + * > :first-child + * > * > :first-child + *') ( | css.nest('&:indeterminate[type="checkbox"] + * > :first-child + * > * > :first-child + *') ( | ||||
css.if ( | css.if ( | ||||
appearance === CheckControlAppearance.BUTTON | appearance === CheckControlAppearance.BUTTON | ||||
|| appearance === CheckControlAppearance.DEFAULT | |||||
|| appearance === CheckControlAppearance.TICK_BOX | |||||
) ( | ) ( | ||||
css` | css` | ||||
display: block; | display: block; | ||||
@@ -135,7 +135,7 @@ export const CheckStateContainer = ({ | |||||
css.nest('&:checked + * > :first-child + * > * > :first-child') ( | css.nest('&:checked + * > :first-child + * > * > :first-child') ( | ||||
css.if ( | css.if ( | ||||
appearance === CheckControlAppearance.BUTTON | appearance === CheckControlAppearance.BUTTON | ||||
|| appearance === CheckControlAppearance.DEFAULT | |||||
|| appearance === CheckControlAppearance.TICK_BOX | |||||
) ( | ) ( | ||||
css` | css` | ||||
display: block; | display: block; | ||||
@@ -177,7 +177,7 @@ export const CheckIndicatorArea = ({ | |||||
box-sizing: border-box; | box-sizing: border-box; | ||||
} | } | ||||
`, | `, | ||||
css.if (appearance === CheckControlAppearance.DEFAULT) ( | |||||
css.if (appearance === CheckControlAppearance.TICK_BOX) ( | |||||
css` | css` | ||||
width: 1.5em; | width: 1.5em; | ||||
height: 1.5em; | height: 1.5em; | ||||
@@ -240,7 +240,7 @@ export const CheckIndicatorWrapper = ({ | |||||
border-radius: inherit; | border-radius: inherit; | ||||
`, | `, | ||||
css.if( | css.if( | ||||
appearance === CheckControlAppearance.DEFAULT | |||||
appearance === CheckControlAppearance.TICK_BOX | |||||
|| appearance === CheckControlAppearance.BUTTON | || appearance === CheckControlAppearance.BUTTON | ||||
) ( | ) ( | ||||
css` | css` | ||||
@@ -289,7 +289,7 @@ export const ClickAreaWrapper = ({ | |||||
css.dynamic({ | css.dynamic({ | ||||
display: block ? 'block' : 'inline-block', | display: block ? 'block' : 'inline-block', | ||||
}), | }), | ||||
css.if (appearance === CheckControlAppearance.DEFAULT) ( | |||||
css.if (appearance === CheckControlAppearance.TICK_BOX) ( | |||||
css` | css` | ||||
padding-left: 2.25rem; | padding-left: 2.25rem; | ||||
text-indent: -2.25rem; | text-indent: -2.25rem; | ||||
@@ -57,7 +57,7 @@ export type TextControlBaseArgs = { | |||||
predefinedValues: boolean, | predefinedValues: boolean, | ||||
} | } | ||||
export const ComponentBase = ({ | |||||
export const Root = ({ | |||||
block, | block, | ||||
}: TextControlBaseArgs): string => css.cx( | }: TextControlBaseArgs): string => css.cx( | ||||
css` | css` | ||||
@@ -365,3 +365,15 @@ export const IndicatorWrapper = ({ | |||||
height: MIN_HEIGHTS[size], | height: MIN_HEIGHTS[size], | ||||
}), | }), | ||||
); | ); | ||||
export const Indicator = (): string => css.cx( | |||||
css` | |||||
width: 1.5em; | |||||
height: 1.5em; | |||||
fill: none; | |||||
stroke: currentColor; | |||||
stroke-width: 2; | |||||
stroke-linecap: round; | |||||
stroke-linejoin: round; | |||||
`, | |||||
); |
@@ -0,0 +1,22 @@ | |||||
import * as ButtonBase from '@tesseract-design/web-base-button'; | |||||
// TODO check if a utility library like this is needed! | |||||
export type ButtonBaseProps<Node> = { | |||||
/** | |||||
* Size of the component. | |||||
*/ | |||||
size?: ButtonBase.ButtonSize, | |||||
/** | |||||
* Variant of the component. | |||||
*/ | |||||
variant?: ButtonBase.ButtonVariant, | |||||
/** | |||||
* Should the component display a border? | |||||
*/ | |||||
border?: boolean, | |||||
/** | |||||
* Short complementary content displayed at the edge of the component. | |||||
*/ | |||||
badge?: Node, | |||||
} |
@@ -1,5 +1,11 @@ | |||||
import { css, CssIfStringImpl, CssStringImpl } from '.'; | import { css, CssIfStringImpl, CssStringImpl } from '.'; | ||||
jest.mock('goober', () => { | |||||
return { | |||||
css: () => 'gooberClass', | |||||
}; | |||||
}); | |||||
describe('css-utils', () => { | describe('css-utils', () => { | ||||
describe('css', () => { | describe('css', () => { | ||||
it('should return CssString', () => { | it('should return CssString', () => { | ||||
@@ -9,7 +15,7 @@ describe('css-utils', () => { | |||||
` | ` | ||||
expect(c).toBeInstanceOf(CssStringImpl); | expect(c).toBeInstanceOf(CssStringImpl); | ||||
expect(c.toString()).toBe('background-color:white; color:black;'); | |||||
expect(c.toString()).toBe('background-color:white;color:black;'); | |||||
}) | }) | ||||
}) | }) | ||||
@@ -35,7 +41,7 @@ describe('css-utils', () => { | |||||
expect(c.toString()).toBe('') | expect(c.toString()).toBe('') | ||||
}) | }) | ||||
it('should return CssString with .else when the condition is true', () => { | |||||
it('should return CssString with .else when the if condition is false', () => { | |||||
const c = css.if(false)( | const c = css.if(false)( | ||||
css` | css` | ||||
background-color: white; | background-color: white; | ||||
@@ -46,42 +52,21 @@ describe('css-utils', () => { | |||||
` | ` | ||||
) | ) | ||||
expect(c).toBeInstanceOf(CssStringImpl) | |||||
expect(c.toString()).toBe('background-color:black;') | |||||
}) | |||||
it('should return CssString with .else.if when the condition is true', () => { | |||||
const c = css.if(false)( | |||||
css` | |||||
background-color: white; | |||||
` | |||||
).else.if(true)( | |||||
css` | |||||
background-color: black; | |||||
` | |||||
) | |||||
expect(c).toBeInstanceOf(CssIfStringImpl) | |||||
expect(c.toString()).toBe('background-color:black;') | expect(c.toString()).toBe('background-color:black;') | ||||
}) | }) | ||||
it('should return CssString with .else.if.else when the condition is false', () => { | |||||
const c = css.if('a'.toUpperCase() === 'C')( | |||||
it('should return CssString with .else when the if condition is true', () => { | |||||
const c = css.if(true)( | |||||
css` | css` | ||||
background-color: white; | background-color: white; | ||||
` | ` | ||||
).else.if('b'.toUpperCase() === 'C')( | |||||
css` | |||||
background-color: black; | |||||
` | |||||
).else( | ).else( | ||||
css` | css` | ||||
background-color: gray; | |||||
background-color: black; | |||||
` | ` | ||||
) | ) | ||||
expect(c).toBeInstanceOf(CssStringImpl) | |||||
expect(c.toString()).toBe('background-color:gray;') | |||||
expect(c.toString()).toBe('background-color:white;') | |||||
}) | }) | ||||
}) | }) | ||||
@@ -118,7 +103,35 @@ describe('css-utils', () => { | |||||
` | ` | ||||
) | ) | ||||
expect(c.toString()).toBe('@media only screen and (min-width: 720px){color:black; background-color:white;}') | |||||
expect(c.toString()).toBe('@media only screen and (min-width: 720px){color:black;background-color:white;}') | |||||
}) | |||||
}) | |||||
describe('css.cx', () => { | |||||
it('should accept strings as classnames', () => { | |||||
expect(css.cx('class1', 'class2')).toBe('class1 class2'); | |||||
}) | |||||
it('should accept CSS strings for classname generation', () => { | |||||
expect( | |||||
css.cx( | |||||
css` | |||||
color: white; | |||||
` | |||||
) | |||||
).toBe('gooberClass'); | |||||
}) | |||||
it('should accept mixed values', () => { | |||||
expect( | |||||
css.cx( | |||||
'class1', | |||||
'class2', | |||||
css` | |||||
color: white; | |||||
` | |||||
) | |||||
).toBe('class1 class2 gooberClass'); | |||||
}) | }) | ||||
}) | }) | ||||
}) | }) |
@@ -1,18 +1,198 @@ | |||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { | import { | ||||
render, | render, | ||||
screen | |||||
screen, | |||||
} from '@testing-library/react'; | } from '@testing-library/react'; | ||||
import '@testing-library/jest-dom'; | import '@testing-library/jest-dom'; | ||||
import userEvent from '@testing-library/user-event'; | |||||
import * as TextControlBase from '@tesseract-design/web-base-textcontrol'; | |||||
import { | import { | ||||
MaskedTextInput | MaskedTextInput | ||||
} from '.'; | } from '.'; | ||||
jest.mock('@tesseract-design/web-base-textcontrol'); | |||||
describe('MaskedTextInput', () => { | describe('MaskedTextInput', () => { | ||||
it('should render an input', () => { | it('should render an input', () => { | ||||
render(<MaskedTextInput />); | |||||
render( | |||||
<MaskedTextInput /> | |||||
); | |||||
const textbox: HTMLInputElement = screen.getByTestId('input'); | const textbox: HTMLInputElement = screen.getByTestId('input'); | ||||
expect(textbox).toBeInTheDocument(); | expect(textbox).toBeInTheDocument(); | ||||
expect(textbox.type).toBe('password'); | |||||
expect(textbox).toHaveProperty('type', 'password'); | |||||
}); | |||||
it('should render a border', () => { | |||||
render( | |||||
<MaskedTextInput | |||||
border | |||||
/> | |||||
); | |||||
const border = screen.getByTestId('border'); | |||||
expect(border).toBeInTheDocument(); | |||||
}); | |||||
it('should render a label', () => { | |||||
render( | |||||
<MaskedTextInput | |||||
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( | |||||
<MaskedTextInput | |||||
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( | |||||
<MaskedTextInput | |||||
hint="foo" | |||||
/> | |||||
); | |||||
const hint = screen.getByTestId('hint'); | |||||
expect(hint).toBeInTheDocument(); | |||||
}); | |||||
it('should render an indicator', () => { | |||||
render( | |||||
<MaskedTextInput | |||||
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( | |||||
<MaskedTextInput | |||||
size={size} | |||||
/> | |||||
); | |||||
expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render hint styles', () => { | |||||
render( | |||||
<MaskedTextInput | |||||
size={size} | |||||
hint="hint" | |||||
/> | |||||
); | |||||
expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render indicator styles', () => { | |||||
render( | |||||
<MaskedTextInput | |||||
size={size} | |||||
indicator={ | |||||
<div data-testid="indicator" /> | |||||
} | |||||
/> | |||||
); | |||||
expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
}); | |||||
it('should render a block textbox', () => { | |||||
render( | |||||
<MaskedTextInput | |||||
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( | |||||
<MaskedTextInput | |||||
style={style} | |||||
/> | |||||
); | |||||
expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
it('should render hint styles', () => { | |||||
render( | |||||
<MaskedTextInput | |||||
style={style} | |||||
hint="hint" | |||||
/> | |||||
); | |||||
expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
it('should render indicator styles', () => { | |||||
render( | |||||
<MaskedTextInput | |||||
style={style} | |||||
indicator={ | |||||
<div | |||||
data-testid="indicator" | |||||
/> | |||||
} | |||||
/> | |||||
); | |||||
expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
}); | |||||
it('should handle change events', () => { | |||||
const onChange = jest.fn(); | |||||
render( | |||||
<MaskedTextInput | |||||
onChange={onChange} | |||||
/> | |||||
); | |||||
const textbox: HTMLInputElement = screen.getByTestId('input'); | |||||
userEvent.type(textbox, 'foobar'); | |||||
expect(onChange).toBeCalled(); | |||||
}); | }); | ||||
}); | }); |
@@ -71,7 +71,7 @@ export const MaskedTextInput = React.forwardRef<HTMLInputElement, MaskedTextInpu | |||||
return ( | return ( | ||||
<div | <div | ||||
className={TextControlBase.ComponentBase(textInputBaseArgs)} | |||||
className={TextControlBase.Root(textInputBaseArgs)} | |||||
> | > | ||||
<input | <input | ||||
{...etcProps} | {...etcProps} | ||||
@@ -81,10 +81,17 @@ export const MaskedTextInput = React.forwardRef<HTMLInputElement, MaskedTextInpu | |||||
type="password" | type="password" | ||||
data-testid="input" | data-testid="input" | ||||
/> | /> | ||||
{border && <span />} | |||||
{ | |||||
border && ( | |||||
<span | |||||
data-testid="border" | |||||
/> | |||||
) | |||||
} | |||||
{ | { | ||||
label && !hiddenLabel && ( | label && !hiddenLabel && ( | ||||
<div | <div | ||||
data-testid="label" | |||||
className={TextControlBase.LabelWrapper(textInputBaseArgs)} | className={TextControlBase.LabelWrapper(textInputBaseArgs)} | ||||
> | > | ||||
{label} | {label} | ||||
@@ -94,6 +101,7 @@ export const MaskedTextInput = React.forwardRef<HTMLInputElement, MaskedTextInpu | |||||
{hint && ( | {hint && ( | ||||
<div | <div | ||||
className={TextControlBase.HintWrapper(textInputBaseArgs)} | className={TextControlBase.HintWrapper(textInputBaseArgs)} | ||||
data-testid="hint" | |||||
> | > | ||||
<div | <div | ||||
className={TextControlBase.Hint()} | className={TextControlBase.Hint()} | ||||
@@ -4,14 +4,192 @@ import { | |||||
screen | screen | ||||
} from '@testing-library/react'; | } from '@testing-library/react'; | ||||
import '@testing-library/jest-dom'; | import '@testing-library/jest-dom'; | ||||
import userEvent from '@testing-library/user-event'; | |||||
import * as TextControlBase from '@tesseract-design/web-base-textcontrol'; | |||||
import { | import { | ||||
MultilineTextInput | MultilineTextInput | ||||
} from '.'; | } from '.'; | ||||
jest.mock('@tesseract-design/web-base-textcontrol'); | |||||
describe('MultilineTextInput', () => { | describe('MultilineTextInput', () => { | ||||
it('should render a textbox', () => { | it('should render a textbox', () => { | ||||
render(<MultilineTextInput />); | render(<MultilineTextInput />); | ||||
const textbox = screen.getByRole('textbox'); | |||||
const textbox: HTMLTextAreaElement = screen.getByRole('textbox'); | |||||
expect(textbox).toBeInTheDocument(); | |||||
}); | |||||
it('should render a border', () => { | |||||
render( | |||||
<MultilineTextInput | |||||
border | |||||
/> | |||||
); | |||||
const border = screen.getByTestId('border'); | |||||
expect(border).toBeInTheDocument(); | |||||
}); | |||||
it('should render a label', () => { | |||||
render( | |||||
<MultilineTextInput | |||||
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( | |||||
<MultilineTextInput | |||||
label="foo" | |||||
hiddenLabel | |||||
/> | |||||
); | |||||
const textbox = screen.getByLabelText('foo'); | |||||
expect(textbox).toBeInTheDocument(); | expect(textbox).toBeInTheDocument(); | ||||
const label = screen.queryByTestId('label'); | |||||
expect(label).toBeNull(); | |||||
}); | |||||
it('should render a hint', () => { | |||||
render( | |||||
<MultilineTextInput | |||||
hint="foo" | |||||
/> | |||||
); | |||||
const hint = screen.getByTestId('hint'); | |||||
expect(hint).toBeInTheDocument(); | |||||
}); | |||||
it('should render an indicator', () => { | |||||
render( | |||||
<MultilineTextInput | |||||
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( | |||||
<MultilineTextInput | |||||
size={size} | |||||
/> | |||||
); | |||||
expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render hint styles', () => { | |||||
render( | |||||
<MultilineTextInput | |||||
size={size} | |||||
hint="hint" | |||||
/> | |||||
); | |||||
expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render indicator styles', () => { | |||||
render( | |||||
<MultilineTextInput | |||||
size={size} | |||||
indicator={ | |||||
<div data-testid="indicator" /> | |||||
} | |||||
/> | |||||
); | |||||
expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
}); | |||||
it('should render a block textbox', () => { | |||||
render( | |||||
<MultilineTextInput | |||||
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( | |||||
<MultilineTextInput | |||||
style={style} | |||||
/> | |||||
); | |||||
expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
it('should render hint styles', () => { | |||||
render( | |||||
<MultilineTextInput | |||||
style={style} | |||||
hint="hint" | |||||
/> | |||||
); | |||||
expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
it('should render indicator styles', () => { | |||||
render( | |||||
<MultilineTextInput | |||||
style={style} | |||||
indicator={ | |||||
<div | |||||
data-testid="indicator" | |||||
/> | |||||
} | |||||
/> | |||||
); | |||||
expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
}); | |||||
it('should handle change events', () => { | |||||
const onChange = jest.fn(); | |||||
render( | |||||
<MultilineTextInput | |||||
onChange={onChange} | |||||
/> | |||||
); | |||||
const textbox: HTMLTextAreaElement = screen.getByRole('textbox'); | |||||
userEvent.type(textbox, 'foobar'); | |||||
expect(onChange).toBeCalled(); | |||||
}); | }); | ||||
}); | }); |
@@ -71,7 +71,7 @@ export const MultilineTextInput = React.forwardRef<HTMLTextAreaElement, Multilin | |||||
return ( | return ( | ||||
<div | <div | ||||
className={TextControlBase.ComponentBase(textInputBaseArgs)} | |||||
className={TextControlBase.Root(textInputBaseArgs)} | |||||
> | > | ||||
<textarea | <textarea | ||||
{...etcProps} | {...etcProps} | ||||
@@ -81,11 +81,19 @@ export const MultilineTextInput = React.forwardRef<HTMLTextAreaElement, Multilin | |||||
style={{ | style={{ | ||||
height: TextControlBase.MIN_HEIGHTS[size], | height: TextControlBase.MIN_HEIGHTS[size], | ||||
}} | }} | ||||
data-testid="input" | |||||
/> | /> | ||||
{border && <span />} | |||||
{ | |||||
border && ( | |||||
<span | |||||
data-testid="border" | |||||
/> | |||||
) | |||||
} | |||||
{ | { | ||||
label && !hiddenLabel && ( | label && !hiddenLabel && ( | ||||
<div | <div | ||||
data-testid="label" | |||||
className={TextControlBase.LabelWrapper(textInputBaseArgs)} | className={TextControlBase.LabelWrapper(textInputBaseArgs)} | ||||
> | > | ||||
{label} | {label} | ||||
@@ -95,6 +103,7 @@ export const MultilineTextInput = React.forwardRef<HTMLTextAreaElement, Multilin | |||||
{hint && ( | {hint && ( | ||||
<div | <div | ||||
className={TextControlBase.HintWrapper(textInputBaseArgs)} | className={TextControlBase.HintWrapper(textInputBaseArgs)} | ||||
data-testid="hint" | |||||
> | > | ||||
<div | <div | ||||
className={TextControlBase.Hint()} | className={TextControlBase.Hint()} | ||||
@@ -4,14 +4,208 @@ import { | |||||
screen | screen | ||||
} from '@testing-library/react'; | } from '@testing-library/react'; | ||||
import '@testing-library/jest-dom'; | import '@testing-library/jest-dom'; | ||||
import userEvent from '@testing-library/user-event'; | |||||
import * as TextControlBase from '@tesseract-design/web-base-textcontrol'; | |||||
import { | import { | ||||
TextInput | |||||
TextInput, TextInputType, | |||||
} from '.'; | } from '.'; | ||||
jest.mock('@tesseract-design/web-base-textcontrol'); | |||||
describe('TextInput', () => { | describe('TextInput', () => { | ||||
it('should render a textbox', () => { | it('should render a textbox', () => { | ||||
render(<TextInput />); | |||||
render( | |||||
<TextInput /> | |||||
); | |||||
const textbox = screen.getByRole('textbox'); | const textbox = screen.getByRole('textbox'); | ||||
expect(textbox).toBeInTheDocument(); | expect(textbox).toBeInTheDocument(); | ||||
expect(textbox).toHaveProperty('type', 'text'); | |||||
}); | |||||
it('should render a border', () => { | |||||
render( | |||||
<TextInput | |||||
border | |||||
/> | |||||
); | |||||
const border = screen.getByTestId('border'); | |||||
expect(border).toBeInTheDocument(); | |||||
}); | |||||
it('should render a label', () => { | |||||
render( | |||||
<TextInput | |||||
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( | |||||
<TextInput | |||||
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( | |||||
<TextInput | |||||
hint="foo" | |||||
/> | |||||
); | |||||
const hint = screen.getByTestId('hint'); | |||||
expect(hint).toBeInTheDocument(); | |||||
}); | |||||
it('should render an indicator', () => { | |||||
render( | |||||
<TextInput | |||||
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( | |||||
<TextInput | |||||
size={size} | |||||
/> | |||||
); | |||||
expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render hint styles', () => { | |||||
render( | |||||
<TextInput | |||||
size={size} | |||||
hint="hint" | |||||
/> | |||||
); | |||||
expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render indicator styles', () => { | |||||
render( | |||||
<TextInput | |||||
size={size} | |||||
indicator={ | |||||
<div data-testid="indicator" /> | |||||
} | |||||
/> | |||||
); | |||||
expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
}); | |||||
it('should render a block textbox', () => { | |||||
render( | |||||
<TextInput | |||||
block | |||||
/> | |||||
); | |||||
expect(TextControlBase.Root).toBeCalledWith(expect.objectContaining({ | |||||
block: true, | |||||
})); | |||||
}); | |||||
it.each([ | |||||
TextInputType.TEXT, | |||||
TextInputType.SEARCH, | |||||
])('should render a textbox with type %s', (buttonType) => { | |||||
render( | |||||
<TextInput | |||||
type={buttonType} | |||||
/> | |||||
); | |||||
const textbox: HTMLButtonElement = screen.getByTestId('input'); | |||||
expect(textbox).toHaveProperty('type', buttonType); | |||||
}); | |||||
describe.each([ | |||||
TextControlBase.TextControlStyle.DEFAULT, | |||||
TextControlBase.TextControlStyle.ALTERNATE, | |||||
])('on %s style', (style) => { | |||||
it('should render input styles', () => { | |||||
render( | |||||
<TextInput | |||||
style={style} | |||||
/> | |||||
); | |||||
expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
it('should render hint styles', () => { | |||||
render( | |||||
<TextInput | |||||
style={style} | |||||
hint="hint" | |||||
/> | |||||
); | |||||
expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
it('should render indicator styles', () => { | |||||
render( | |||||
<TextInput | |||||
style={style} | |||||
indicator={ | |||||
<div | |||||
data-testid="indicator" | |||||
/> | |||||
} | |||||
/> | |||||
); | |||||
expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
}); | |||||
it('should handle change events', () => { | |||||
const onChange = jest.fn(); | |||||
render( | |||||
<TextInput | |||||
onChange={onChange} | |||||
/> | |||||
); | |||||
const textbox: HTMLInputElement = screen.getByRole('textbox'); | |||||
userEvent.type(textbox, 'foobar'); | |||||
expect(onChange).toBeCalled(); | |||||
}); | }); | ||||
}); | }); |
@@ -81,7 +81,7 @@ export const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>( | |||||
return ( | return ( | ||||
<div | <div | ||||
className={TextControlBase.ComponentBase(textInputBaseArgs)} | |||||
className={TextControlBase.Root(textInputBaseArgs)} | |||||
> | > | ||||
<input | <input | ||||
{...etcProps} | {...etcProps} | ||||
@@ -89,11 +89,19 @@ export const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>( | |||||
ref={ref} | ref={ref} | ||||
aria-label={label} | aria-label={label} | ||||
type={type} | type={type} | ||||
data-testid="input" | |||||
/> | /> | ||||
{border && <span />} | |||||
{ | |||||
border && ( | |||||
<span | |||||
data-testid="border" | |||||
/> | |||||
) | |||||
} | |||||
{ | { | ||||
label && !hiddenLabel && ( | label && !hiddenLabel && ( | ||||
<div | <div | ||||
data-testid="label" | |||||
className={TextControlBase.LabelWrapper(textInputBaseArgs)} | className={TextControlBase.LabelWrapper(textInputBaseArgs)} | ||||
> | > | ||||
{label} | {label} | ||||
@@ -103,6 +111,7 @@ export const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>( | |||||
{hint && ( | {hint && ( | ||||
<div | <div | ||||
className={TextControlBase.HintWrapper(textInputBaseArgs)} | className={TextControlBase.HintWrapper(textInputBaseArgs)} | ||||
data-testid="hint" | |||||
> | > | ||||
<div | <div | ||||
className={TextControlBase.Hint()} | className={TextControlBase.Hint()} | ||||
@@ -0,0 +1,11 @@ | |||||
import * as WebFreeformReact from '.'; | |||||
describe('web-freeform-react', () => { | |||||
it.each([ | |||||
'MaskedTextInput', | |||||
'MultilineTextInput', | |||||
'TextInput', | |||||
])('should export %s', (namedExport) => { | |||||
expect(WebFreeformReact).toHaveProperty(namedExport); | |||||
}); | |||||
}); |
@@ -1,66 +0,0 @@ | |||||
import * as React from 'react'; | |||||
import { | |||||
css, | |||||
} from 'goober'; | |||||
const BadgeBase = ({ rounded }: { rounded: boolean }) => css` | |||||
position: relative; | |||||
height: 1.5em; | |||||
min-width: 1.5em; | |||||
display: inline-grid; | |||||
vertical-align: middle; | |||||
place-content: center; | |||||
border-radius: ${rounded ? '0.75em' : '0.25rem'}; | |||||
overflow: hidden; | |||||
font-stretch: var(--font-stretch-base, normal); | |||||
padding: 0 0.25rem; | |||||
box-sizing: border-box; | |||||
&::before { | |||||
position: absolute; | |||||
top: 0; | |||||
left: 0; | |||||
width: 100%; | |||||
height: 100%; | |||||
background-color: currentColor; | |||||
opacity: 0.25; | |||||
content: ''; | |||||
} | |||||
`; | |||||
const Content = () => css` | |||||
position: relative; | |||||
font-size: 0.75em; | |||||
`; | |||||
export type BadgeProps = React.HTMLProps<HTMLSpanElement> & { | |||||
rounded?: boolean, | |||||
}; | |||||
export const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>( | |||||
( | |||||
{ | |||||
children, | |||||
rounded = false, | |||||
}, | |||||
ref, | |||||
) => { | |||||
const badgeStyleProps = React.useMemo(() => ({ | |||||
rounded, | |||||
}), [rounded]); | |||||
return ( | |||||
<strong | |||||
ref={ref} | |||||
className={BadgeBase(badgeStyleProps)} | |||||
> | |||||
<span | |||||
className={Content()} | |||||
> | |||||
{children} | |||||
</span> | |||||
</strong> | |||||
) | |||||
} | |||||
) | |||||
Badge.displayName = 'Badge'; |
@@ -0,0 +1,31 @@ | |||||
import * as React from 'react'; | |||||
import { | |||||
render, | |||||
screen, | |||||
} from '@testing-library/react'; | |||||
import '@testing-library/jest-dom'; | |||||
import { | |||||
Badge, | |||||
} from '.'; | |||||
jest.mock('@tesseract-design/web-base-badge'); | |||||
describe('Badge', () => { | |||||
it('should render a badge', () => { | |||||
render( | |||||
<Badge /> | |||||
); | |||||
const button: HTMLButtonElement = screen.getByTestId('badge'); | |||||
expect(button).toBeInTheDocument(); | |||||
}); | |||||
it('should render a rounded badge', () => { | |||||
render( | |||||
<Badge | |||||
rounded | |||||
/> | |||||
); | |||||
const button: HTMLButtonElement = screen.getByTestId('badge'); | |||||
expect(button).toBeInTheDocument(); | |||||
}); | |||||
}); |
@@ -0,0 +1,36 @@ | |||||
import * as React from 'react'; | |||||
import * as BadgeBase from '@tesseract-design/web-base-badge'; | |||||
export type BadgeProps = React.HTMLProps<HTMLSpanElement> & { | |||||
rounded?: boolean, | |||||
}; | |||||
export const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>( | |||||
( | |||||
{ | |||||
children, | |||||
rounded = false, | |||||
}, | |||||
ref, | |||||
) => { | |||||
const badgeStyleProps = React.useMemo<BadgeBase.BadgeBaseArgs>(() => ({ | |||||
rounded, | |||||
}), [rounded]); | |||||
return ( | |||||
<strong | |||||
ref={ref} | |||||
className={BadgeBase.Root(badgeStyleProps)} | |||||
data-testid="badge" | |||||
> | |||||
<span | |||||
className={BadgeBase.Content()} | |||||
> | |||||
{children} | |||||
</span> | |||||
</strong> | |||||
) | |||||
} | |||||
) | |||||
Badge.displayName = 'Badge'; |
@@ -0,0 +1,9 @@ | |||||
import * as WebInformationReact from '.'; | |||||
describe('web-information-react', () => { | |||||
it.each([ | |||||
'Badge', | |||||
])('should export %s', (namedExport) => { | |||||
expect(WebInformationReact).toHaveProperty(namedExport); | |||||
}); | |||||
}); |
@@ -0,0 +1,189 @@ | |||||
import * as React from 'react'; | |||||
import { | |||||
render, | |||||
screen, | |||||
} from '@testing-library/react'; | |||||
import '@testing-library/jest-dom'; | |||||
import { | |||||
LinkButton, | |||||
} from '.'; | |||||
import userEvent from '@testing-library/user-event'; | |||||
import * as ButtonBase from '@tesseract-design/web-base-button'; | |||||
jest.mock('@tesseract-design/web-base-button'); | |||||
describe('LinkButton', () => { | |||||
it('should render a link', () => { | |||||
render( | |||||
<LinkButton | |||||
href="http://example.com" | |||||
/> | |||||
); | |||||
const button: HTMLButtonElement = screen.getByRole('link'); | |||||
expect(button).toBeInTheDocument(); | |||||
}); | |||||
it('should render a subtext', () => { | |||||
render( | |||||
<LinkButton | |||||
subtext="subtext" | |||||
/> | |||||
); | |||||
const subtext: HTMLElement = screen.getByTestId('subtext'); | |||||
expect(subtext).toBeInTheDocument(); | |||||
}); | |||||
it('should render a badge', () => { | |||||
render( | |||||
<LinkButton | |||||
badge="badge" | |||||
/> | |||||
); | |||||
const badge: HTMLElement = screen.getByTestId('badge'); | |||||
expect(badge).toBeInTheDocument(); | |||||
}); | |||||
it('should render as a menu item', () => { | |||||
render( | |||||
<LinkButton | |||||
menuItem | |||||
/> | |||||
); | |||||
const menuItemIndicator: HTMLElement = screen.getByTestId('menuItemIndicator'); | |||||
expect(menuItemIndicator).toBeInTheDocument(); | |||||
}); | |||||
it('should handle click events', () => { | |||||
const onClick = jest.fn(); | |||||
render( | |||||
<LinkButton | |||||
href="http://example.com" | |||||
onClick={onClick} | |||||
/> | |||||
); | |||||
const button: HTMLButtonElement = screen.getByRole('link'); | |||||
userEvent.click(button); | |||||
expect(onClick).toBeCalled(); | |||||
}); | |||||
it('should render a compact button', () => { | |||||
render( | |||||
<LinkButton | |||||
compact | |||||
/> | |||||
); | |||||
expect(ButtonBase.Button).toBeCalledWith(expect.objectContaining({ | |||||
compact: true, | |||||
})); | |||||
expect(ButtonBase.Label).toBeCalledWith(expect.objectContaining({ | |||||
compact: true, | |||||
})); | |||||
}); | |||||
describe.each([ | |||||
ButtonBase.ButtonSize.SMALL, | |||||
ButtonBase.ButtonSize.MEDIUM, | |||||
ButtonBase.ButtonSize.LARGE, | |||||
])('on %s size', (size) => { | |||||
it('should render button styles', () => { | |||||
render( | |||||
<LinkButton | |||||
size={size} | |||||
/> | |||||
); | |||||
expect(ButtonBase.Button).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render badge styles', () => { | |||||
render( | |||||
<LinkButton | |||||
size={size} | |||||
badge="badge" | |||||
/> | |||||
); | |||||
expect(ButtonBase.BadgeContainer).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render indicator styles', () => { | |||||
render( | |||||
<LinkButton | |||||
size={size} | |||||
menuItem | |||||
/> | |||||
); | |||||
expect(ButtonBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
}); | |||||
it.each([ | |||||
ButtonBase.ButtonVariant.OUTLINE, | |||||
ButtonBase.ButtonVariant.FILLED, | |||||
])('should render a button with variant %s', (variant) => { | |||||
render( | |||||
<LinkButton | |||||
variant={variant} | |||||
/> | |||||
); | |||||
expect(ButtonBase.Button).toBeCalledWith(expect.objectContaining({ | |||||
variant, | |||||
})); | |||||
}); | |||||
it('should render a bordered button', () => { | |||||
render( | |||||
<LinkButton | |||||
border | |||||
/> | |||||
); | |||||
expect(ButtonBase.Border).toBeCalledWith(expect.objectContaining({ | |||||
border: true, | |||||
})); | |||||
}); | |||||
it('should render a block button', () => { | |||||
render( | |||||
<LinkButton | |||||
block | |||||
/> | |||||
); | |||||
expect(ButtonBase.Border).toBeCalledWith(expect.objectContaining({ | |||||
block: true, | |||||
})); | |||||
}); | |||||
it('should render children', () => { | |||||
render( | |||||
<LinkButton> | |||||
Foo | |||||
</LinkButton> | |||||
); | |||||
const children: HTMLElement = screen.getByTestId('children'); | |||||
expect(children).toHaveTextContent('Foo'); | |||||
}); | |||||
it('should render a disabled link', () => { | |||||
render( | |||||
<LinkButton | |||||
href="http://example.com" | |||||
disabled | |||||
/> | |||||
); | |||||
const button = screen.queryByRole('link'); | |||||
expect(button).toBeNull(); | |||||
}); | |||||
}); |
@@ -20,14 +20,6 @@ export type LinkButtonProps = Omit<React.HTMLProps<LinkButtonElement>, 'size' | | |||||
* Should the component occupy the whole width of its parent? | * Should the component occupy the whole width of its parent? | ||||
*/ | */ | ||||
block?: boolean, | block?: boolean, | ||||
/** | |||||
* Can the component be activated? | |||||
*/ | |||||
disabled?: boolean, | |||||
/** | |||||
* Style of the component. | |||||
*/ | |||||
style?: ButtonBase.ButtonStyle, | |||||
/** | /** | ||||
* Does the component need to conserve space? | * Does the component need to conserve space? | ||||
*/ | */ | ||||
@@ -60,7 +52,6 @@ export const LinkButton = React.forwardRef<LinkButtonElement, LinkButtonProps>( | |||||
children, | children, | ||||
block = false, | block = false, | ||||
disabled = false, | disabled = false, | ||||
style = ButtonBase.ButtonStyle.DEFAULT, | |||||
onClick, | onClick, | ||||
href, | href, | ||||
target, | target, | ||||
@@ -81,10 +72,9 @@ export const LinkButton = React.forwardRef<LinkButtonElement, LinkButtonProps>( | |||||
variant, | variant, | ||||
border, | border, | ||||
disabled, | disabled, | ||||
style, | |||||
compact, | compact, | ||||
menuItem, | menuItem, | ||||
}), [size, block, variant, border, disabled, style, compact, menuItem]); | |||||
}), [size, block, variant, border, disabled, compact, menuItem]); | |||||
const commonChildren = ( | const commonChildren = ( | ||||
<> | <> | ||||
@@ -96,6 +86,7 @@ export const LinkButton = React.forwardRef<LinkButtonElement, LinkButtonProps>( | |||||
> | > | ||||
<span | <span | ||||
className={ButtonBase.MainText()} | className={ButtonBase.MainText()} | ||||
data-testid="children" | |||||
> | > | ||||
<span | <span | ||||
className={ButtonBase.OverflowText()} | className={ButtonBase.OverflowText()} | ||||
@@ -110,6 +101,7 @@ export const LinkButton = React.forwardRef<LinkButtonElement, LinkButtonProps>( | |||||
{' '} | {' '} | ||||
<span | <span | ||||
className={ButtonBase.Subtext()} | className={ButtonBase.Subtext()} | ||||
data-testid="subtext" | |||||
> | > | ||||
<span | <span | ||||
className={ButtonBase.OverflowText()} | className={ButtonBase.OverflowText()} | ||||
@@ -128,6 +120,7 @@ export const LinkButton = React.forwardRef<LinkButtonElement, LinkButtonProps>( | |||||
{' '} | {' '} | ||||
<span | <span | ||||
className={ButtonBase.BadgeContainer(styleProps)} | className={ButtonBase.BadgeContainer(styleProps)} | ||||
data-testid="badge" | |||||
> | > | ||||
{badge} | {badge} | ||||
</span> | </span> | ||||
@@ -141,6 +134,7 @@ export const LinkButton = React.forwardRef<LinkButtonElement, LinkButtonProps>( | |||||
{' '} | {' '} | ||||
<span | <span | ||||
className={ButtonBase.IndicatorWrapper(styleProps)} | className={ButtonBase.IndicatorWrapper(styleProps)} | ||||
data-testid="menuItemIndicator" | |||||
> | > | ||||
<svg | <svg | ||||
className={ButtonBase.Indicator()} | className={ButtonBase.Indicator()} | ||||
@@ -0,0 +1,9 @@ | |||||
import * as WebNavigationReact from '.'; | |||||
describe('web-navigation-react', () => { | |||||
it.each([ | |||||
'LinkButton', | |||||
])('should export %s', (namedExport) => { | |||||
expect(WebNavigationReact).toHaveProperty(namedExport); | |||||
}); | |||||
}); |
@@ -1,277 +0,0 @@ | |||||
import * as React from 'react'; | |||||
import * as ButtonBase from '@tesseract-design/web-base-button'; | |||||
import * as CheckControlBase from '@tesseract-design/web-base-checkcontrol'; | |||||
export type CheckboxProps = Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'type' | 'style'> & { | |||||
/** | |||||
* Size of the component. | |||||
*/ | |||||
size?: ButtonBase.ButtonSize, | |||||
/** | |||||
* Variant of the component. | |||||
*/ | |||||
variant?: ButtonBase.ButtonVariant, | |||||
/** | |||||
* 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?: ButtonBase.ButtonStyle, | |||||
/** | |||||
* Does the component need to conserve space? | |||||
*/ | |||||
compact?: boolean, | |||||
/** | |||||
* Complementary content of the component. | |||||
*/ | |||||
subtext?: React.ReactNode, | |||||
/** | |||||
* Short complementary content displayed at the edge of the component. | |||||
*/ | |||||
badge?: React.ReactNode, | |||||
/** | |||||
* Appearance of the component. | |||||
*/ | |||||
appearance?: CheckControlBase.CheckControlAppearance, | |||||
/** | |||||
* Does the component have indeterminate check state? | |||||
*/ | |||||
indeterminate?: boolean, | |||||
} | |||||
/** | |||||
* Component for performing an action upon activation (e.g. when clicked). | |||||
* | |||||
* This component functions as a regular button. | |||||
*/ | |||||
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>( | |||||
( | |||||
{ | |||||
size = ButtonBase.ButtonSize.MEDIUM, | |||||
variant = ButtonBase.ButtonVariant.OUTLINE, | |||||
border = false, | |||||
children, | |||||
block = false, | |||||
style = ButtonBase.ButtonStyle.DEFAULT, | |||||
disabled = false, | |||||
compact = false, | |||||
subtext, | |||||
badge, | |||||
appearance = CheckControlBase.CheckControlAppearance.DEFAULT, | |||||
indeterminate = false, | |||||
className: _className, | |||||
as: _as, | |||||
...etcProps | |||||
}: CheckboxProps, | |||||
ref, | |||||
) => { | |||||
const styleProps = React.useMemo<ButtonBase.ButtonBaseArgs & CheckControlBase.CheckControlBaseArgs>(() => ({ | |||||
size, | |||||
block, | |||||
variant, | |||||
border, | |||||
style, | |||||
compact, | |||||
menuItem: false, | |||||
disabled, | |||||
appearance, | |||||
type: 'checkbox', | |||||
}), [size, block, variant, border, style, compact, disabled, appearance]); | |||||
const defaultRef = React.useRef<HTMLInputElement>(null); | |||||
const theRef = (ref ?? defaultRef) as React.MutableRefObject<HTMLInputElement>; | |||||
React.useEffect(() => { | |||||
if (!(indeterminate && theRef.current)) { | |||||
return; | |||||
} | |||||
theRef.current.indeterminate = indeterminate; | |||||
}, [theRef, indeterminate]); | |||||
const checkIndicatorCommon = ( | |||||
<span | |||||
className={CheckControlBase.CheckIndicatorArea(styleProps)} | |||||
> | |||||
<span | |||||
className={CheckControlBase.CheckIndicatorWrapper(styleProps)} | |||||
> | |||||
<svg | |||||
className={CheckControlBase.CheckIndicator(styleProps)} | |||||
viewBox="0 0 24 24" | |||||
role="presentation" | |||||
> | |||||
<polyline | |||||
points="20 6 9 17 4 12" | |||||
/> | |||||
</svg> | |||||
<svg | |||||
className={CheckControlBase.CheckIndicator(styleProps)} | |||||
viewBox="0 0 24 24" | |||||
role="presentation" | |||||
> | |||||
<polyline | |||||
points="20 12 4 12" | |||||
/> | |||||
</svg> | |||||
</span> | |||||
</span> | |||||
) | |||||
return ( | |||||
<div | |||||
className={CheckControlBase.ClickAreaWrapper(styleProps)} | |||||
> | |||||
<label | |||||
className={CheckControlBase.ClickArea()} | |||||
> | |||||
<input | |||||
{...etcProps} | |||||
disabled={disabled} | |||||
type="checkbox" | |||||
ref={theRef} | |||||
className={CheckControlBase.CheckStateContainer(styleProps)} | |||||
/> | |||||
{ | |||||
appearance === CheckControlBase.CheckControlAppearance.DEFAULT | |||||
&& ( | |||||
<span> | |||||
<span /> | |||||
{checkIndicatorCommon} | |||||
<span> | |||||
{children} | |||||
{ | |||||
subtext | |||||
&& ( | |||||
<> | |||||
<br /> | |||||
<span | |||||
className={CheckControlBase.Subtext()} | |||||
> | |||||
{subtext} | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
{ | |||||
badge | |||||
&& ( | |||||
<> | |||||
{' '} | |||||
<span | |||||
className={ButtonBase.BadgeContainer(styleProps)} | |||||
> | |||||
{badge} | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
) | |||||
} | |||||
{ | |||||
appearance === CheckControlBase.CheckControlAppearance.BUTTON | |||||
&& ( | |||||
<span | |||||
className={ButtonBase.Button(styleProps)} | |||||
> | |||||
<span | |||||
className={ButtonBase.Border(styleProps)} | |||||
/> | |||||
{checkIndicatorCommon} | |||||
<span | |||||
className={ButtonBase.Label(styleProps)} | |||||
> | |||||
<span | |||||
className={ButtonBase.MainText()} | |||||
> | |||||
<span | |||||
className={ButtonBase.OverflowText()} | |||||
> | |||||
{children} | |||||
</span> | |||||
</span> | |||||
{ | |||||
subtext | |||||
&& ( | |||||
<> | |||||
{' '} | |||||
<span | |||||
className={ButtonBase.Subtext()} | |||||
> | |||||
<span | |||||
className={ButtonBase.OverflowText()} | |||||
> | |||||
{subtext} | |||||
</span> | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
{ | |||||
badge | |||||
&& ( | |||||
<> | |||||
{' '} | |||||
<span | |||||
className={ButtonBase.BadgeContainer(styleProps)} | |||||
> | |||||
{badge} | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
) | |||||
} | |||||
{ | |||||
appearance === CheckControlBase.CheckControlAppearance.SWITCH | |||||
&& ( | |||||
<span> | |||||
<span /> | |||||
{checkIndicatorCommon} | |||||
<span> | |||||
{children} | |||||
{ | |||||
subtext | |||||
&& ( | |||||
<> | |||||
<br /> | |||||
<span | |||||
className={CheckControlBase.Subtext()} | |||||
> | |||||
{subtext} | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
{ | |||||
badge | |||||
&& ( | |||||
<> | |||||
{' '} | |||||
<span | |||||
className={ButtonBase.BadgeContainer(styleProps)} | |||||
> | |||||
{badge} | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
) | |||||
} | |||||
</label> | |||||
</div> | |||||
); | |||||
}, | |||||
); | |||||
Checkbox.displayName = 'ActionButton'; |
@@ -4,14 +4,279 @@ import { | |||||
screen | screen | ||||
} from '@testing-library/react'; | } from '@testing-library/react'; | ||||
import '@testing-library/jest-dom'; | import '@testing-library/jest-dom'; | ||||
import * as TextControlBase from '@tesseract-design/web-base-textcontrol'; | |||||
import { | import { | ||||
DropdownSelect | DropdownSelect | ||||
} from '.'; | } from '.'; | ||||
jest.mock('@tesseract-design/web-base-textcontrol'); | |||||
describe('DropdownSelect', () => { | describe('DropdownSelect', () => { | ||||
it('should render a combobox', () => { | it('should render a combobox', () => { | ||||
render(<DropdownSelect />); | render(<DropdownSelect />); | ||||
const combobox = screen.getByRole('combobox'); | const combobox = screen.getByRole('combobox'); | ||||
expect(combobox).toBeInTheDocument(); | expect(combobox).toBeInTheDocument(); | ||||
}); | }); | ||||
it('should render a border', () => { | |||||
render( | |||||
<DropdownSelect | |||||
border | |||||
/> | |||||
); | |||||
const border = screen.getByTestId('border'); | |||||
expect(border).toBeInTheDocument(); | |||||
}); | |||||
it('should render a label', () => { | |||||
render( | |||||
<DropdownSelect | |||||
label="foo" | |||||
/> | |||||
); | |||||
const combobox = screen.getByLabelText('foo'); | |||||
expect(combobox).toBeInTheDocument(); | |||||
const label = screen.getByTestId('label'); | |||||
expect(label).toHaveTextContent('foo'); | |||||
}); | |||||
it('should render a hidden label', () => { | |||||
render( | |||||
<DropdownSelect | |||||
label="foo" | |||||
hiddenLabel | |||||
/> | |||||
); | |||||
const combobox = screen.getByLabelText('foo'); | |||||
expect(combobox).toBeInTheDocument(); | |||||
const label = screen.queryByTestId('label'); | |||||
expect(label).toBeNull(); | |||||
}); | |||||
it('should render a hint', () => { | |||||
render( | |||||
<DropdownSelect | |||||
hint="foo" | |||||
/> | |||||
); | |||||
const hint = screen.getByTestId('hint'); | |||||
expect(hint).toBeInTheDocument(); | |||||
}); | |||||
it('should not render invalid options', () => { | |||||
render( | |||||
<DropdownSelect | |||||
options={[ | |||||
{ | |||||
label: 'foo', | |||||
}, | |||||
{ | |||||
label: 'bar', | |||||
} | |||||
]} | |||||
/> | |||||
); | |||||
const combobox = screen.getByRole('combobox'); | |||||
expect(combobox.children).toHaveLength(0); | |||||
}); | |||||
it('should render valid options', () => { | |||||
render( | |||||
<DropdownSelect | |||||
options={[ | |||||
{ | |||||
label: 'foo', | |||||
value: 'foo', | |||||
}, | |||||
{ | |||||
label: 'bar', | |||||
value: 'bar', | |||||
} | |||||
]} | |||||
/> | |||||
); | |||||
const combobox = screen.getByRole('combobox'); | |||||
expect(combobox.children).toHaveLength(2); | |||||
}); | |||||
it('should render shallow option groups', () => { | |||||
render( | |||||
<DropdownSelect | |||||
options={[ | |||||
{ | |||||
label: 'foo', | |||||
children: [ | |||||
{ | |||||
label: 'baz', | |||||
value: 'baz', | |||||
}, | |||||
], | |||||
}, | |||||
{ | |||||
label: 'bar', | |||||
children: [ | |||||
{ | |||||
label: 'quux', | |||||
value: 'quux', | |||||
}, | |||||
{ | |||||
label: 'quuux', | |||||
value: 'quuux', | |||||
}, | |||||
], | |||||
} | |||||
]} | |||||
/> | |||||
); | |||||
const combobox = screen.getByRole('combobox'); | |||||
expect(combobox.children).toHaveLength(2); | |||||
expect(combobox.children[0].children).toHaveLength(1); | |||||
expect(combobox.children[1].children).toHaveLength(2); | |||||
}); | |||||
it('should render deep option groups', () => { | |||||
render( | |||||
<DropdownSelect | |||||
options={[ | |||||
{ | |||||
label: 'foo', | |||||
children: [ | |||||
{ | |||||
label: 'baz', | |||||
children: [ | |||||
{ | |||||
label: 'quuuux', | |||||
value: 'quuuux', | |||||
}, | |||||
{ | |||||
label: 'quuuuux', | |||||
value: 'quuuuux', | |||||
}, | |||||
{ | |||||
label: 'quuuuuux', | |||||
value: 'quuuuuux', | |||||
}, | |||||
], | |||||
}, | |||||
], | |||||
}, | |||||
{ | |||||
label: 'bar', | |||||
children: [ | |||||
{ | |||||
label: 'quux', | |||||
value: 'quux', | |||||
}, | |||||
{ | |||||
label: 'quuux', | |||||
value: 'quuux', | |||||
}, | |||||
], | |||||
} | |||||
]} | |||||
/> | |||||
); | |||||
const combobox = screen.getByRole('combobox'); | |||||
expect(combobox.children).toHaveLength(2); | |||||
expect(combobox.children[0].children).toHaveLength(4); | |||||
expect(combobox.children[1].children).toHaveLength(2); | |||||
}); | |||||
describe.each([ | |||||
TextControlBase.TextControlSize.SMALL, | |||||
TextControlBase.TextControlSize.MEDIUM, | |||||
TextControlBase.TextControlSize.LARGE, | |||||
])('on %s size', (size) => { | |||||
it('should render input styles', () => { | |||||
render( | |||||
<DropdownSelect | |||||
size={size} | |||||
/> | |||||
); | |||||
expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render hint styles', () => { | |||||
render( | |||||
<DropdownSelect | |||||
size={size} | |||||
hint="hint" | |||||
/> | |||||
); | |||||
expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
it('should render indicator styles', () => { | |||||
render( | |||||
<DropdownSelect | |||||
size={size} | |||||
/> | |||||
); | |||||
expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({ | |||||
size, | |||||
})); | |||||
}); | |||||
}); | |||||
it('should render a block textbox', () => { | |||||
render( | |||||
<DropdownSelect | |||||
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( | |||||
<DropdownSelect | |||||
style={style} | |||||
/> | |||||
); | |||||
expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
it('should render hint styles', () => { | |||||
render( | |||||
<DropdownSelect | |||||
style={style} | |||||
hint="hint" | |||||
/> | |||||
); | |||||
expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
it('should render indicator styles', () => { | |||||
render( | |||||
<DropdownSelect | |||||
style={style} | |||||
/> | |||||
); | |||||
expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({ | |||||
style, | |||||
})); | |||||
}); | |||||
}); | |||||
}); | }); |
@@ -1,19 +1,6 @@ | |||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { | |||||
css | |||||
} from 'goober'; | |||||
import * as TextControlBase from '@tesseract-design/web-base-textcontrol'; | import * as TextControlBase from '@tesseract-design/web-base-textcontrol'; | ||||
const Indicator = () => css` | |||||
width: 1.5em; | |||||
height: 1.5em; | |||||
fill: none; | |||||
stroke: currentColor; | |||||
stroke-width: 2; | |||||
stroke-linecap: round; | |||||
stroke-linejoin: round; | |||||
`; | |||||
export interface SelectOption { | export interface SelectOption { | ||||
label: string, | label: string, | ||||
value?: string | number | readonly string[] | value?: string | number | readonly string[] | ||||
@@ -22,12 +9,8 @@ export interface SelectOption { | |||||
type RenderOptionsProps = { | type RenderOptionsProps = { | ||||
options: SelectOption[], | options: SelectOption[], | ||||
// FIXME bug in eslint does not play well with React.VFC | |||||
// eslint-disable-next-line react/require-default-props | |||||
optionComponent?: React.ElementType, | optionComponent?: React.ElementType, | ||||
// eslint-disable-next-line react/require-default-props | |||||
optgroupComponent?: React.ElementType, | optgroupComponent?: React.ElementType, | ||||
// eslint-disable-next-line react/require-default-props | |||||
level?: number, | level?: number, | ||||
} | } | ||||
@@ -62,6 +45,7 @@ const RenderOptions: React.VFC<RenderOptionsProps> = ({ | |||||
options={o.children} | options={o.children} | ||||
optionComponent={Option} | optionComponent={Option} | ||||
optgroupComponent={Optgroup} | optgroupComponent={Optgroup} | ||||
level={level + 1} | |||||
/> | /> | ||||
</Optgroup> | </Optgroup> | ||||
); | ); | ||||
@@ -79,6 +63,7 @@ const RenderOptions: React.VFC<RenderOptionsProps> = ({ | |||||
options={o.children} | options={o.children} | ||||
optionComponent={Option} | optionComponent={Option} | ||||
optgroupComponent={Optgroup} | optgroupComponent={Optgroup} | ||||
level={level + 1} | |||||
/> | /> | ||||
</React.Fragment> | </React.Fragment> | ||||
); | ); | ||||
@@ -123,10 +108,6 @@ export type DropdownSelectProps = Omit<React.HTMLProps<HTMLSelectElement>, 'size | |||||
* Options available for the component's values. | * Options available for the component's values. | ||||
*/ | */ | ||||
options?: SelectOption[], | options?: SelectOption[], | ||||
/** | |||||
* Component for rendering options. | |||||
*/ | |||||
renderOptions?: React.ElementType, | |||||
} | } | ||||
/** | /** | ||||
@@ -149,7 +130,6 @@ export const DropdownSelect = React.forwardRef<HTMLSelectElement, DropdownSelect | |||||
placeholder: _placeholder, | placeholder: _placeholder, | ||||
as: _as, | as: _as, | ||||
options = [], | options = [], | ||||
renderOptions: Render = RenderOptions, | |||||
...etcProps | ...etcProps | ||||
}: DropdownSelectProps, | }: DropdownSelectProps, | ||||
ref, | ref, | ||||
@@ -166,7 +146,7 @@ export const DropdownSelect = React.forwardRef<HTMLSelectElement, DropdownSelect | |||||
return ( | return ( | ||||
<div | <div | ||||
className={TextControlBase.ComponentBase(styleArgs)} | |||||
className={TextControlBase.Root(styleArgs)} | |||||
> | > | ||||
<select | <select | ||||
{...etcProps} | {...etcProps} | ||||
@@ -174,14 +154,19 @@ export const DropdownSelect = React.forwardRef<HTMLSelectElement, DropdownSelect | |||||
ref={ref} | ref={ref} | ||||
aria-label={label} | aria-label={label} | ||||
> | > | ||||
<Render | |||||
<RenderOptions | |||||
options={options} | options={options} | ||||
/> | /> | ||||
</select> | </select> | ||||
{border && <span />} | |||||
{border && ( | |||||
<span | |||||
data-testid="border" | |||||
/> | |||||
)} | |||||
{label && !hiddenLabel && ( | {label && !hiddenLabel && ( | ||||
<div | <div | ||||
className={TextControlBase.LabelWrapper(styleArgs)} | className={TextControlBase.LabelWrapper(styleArgs)} | ||||
data-testid="label" | |||||
> | > | ||||
{label} | {label} | ||||
</div> | </div> | ||||
@@ -189,6 +174,7 @@ export const DropdownSelect = React.forwardRef<HTMLSelectElement, DropdownSelect | |||||
{hint && ( | {hint && ( | ||||
<div | <div | ||||
className={TextControlBase.HintWrapper(styleArgs)} | className={TextControlBase.HintWrapper(styleArgs)} | ||||
data-testid="hint" | |||||
> | > | ||||
<div | <div | ||||
className={TextControlBase.Hint()} | className={TextControlBase.Hint()} | ||||
@@ -201,7 +187,7 @@ export const DropdownSelect = React.forwardRef<HTMLSelectElement, DropdownSelect | |||||
className={TextControlBase.IndicatorWrapper(styleArgs)} | className={TextControlBase.IndicatorWrapper(styleArgs)} | ||||
> | > | ||||
<svg | <svg | ||||
className={Indicator()} | |||||
className={TextControlBase.Indicator()} | |||||
viewBox="0 0 24 24" | viewBox="0 0 24 24" | ||||
role="presentation" | role="presentation" | ||||
> | > | ||||
@@ -0,0 +1,22 @@ | |||||
import * as React from 'react'; | |||||
import { | |||||
render, | |||||
screen | |||||
} from '@testing-library/react'; | |||||
import '@testing-library/jest-dom'; | |||||
import * as ButtonBase from '@tesseract-design/web-base-button'; | |||||
import * as CheckControlBase from '@tesseract-design/web-base-checkcontrol'; | |||||
import { RadioButton } from '.'; | |||||
jest.mock('@tesseract-design/web-base-button'); | |||||
jest.mock('@tesseract-design/web-base-checkcontrol'); | |||||
describe('RadioButton', () => { | |||||
it('should render a radio button', () => { | |||||
render( | |||||
<RadioButton /> | |||||
); | |||||
const checkbox = screen.getByRole('radio'); | |||||
expect(checkbox).toBeInTheDocument(); | |||||
}); | |||||
}); |
@@ -19,10 +19,6 @@ export type RadioButtonProps = Omit<React.HTMLProps<HTMLInputElement>, 'size' | | |||||
* Should the component occupy the whole width of its parent? | * Should the component occupy the whole width of its parent? | ||||
*/ | */ | ||||
block?: boolean, | block?: boolean, | ||||
/** | |||||
* Style of the component. | |||||
*/ | |||||
style?: ButtonBase.ButtonStyle, | |||||
/** | /** | ||||
* Does the component need to conserve space? | * Does the component need to conserve space? | ||||
*/ | */ | ||||
@@ -35,10 +31,6 @@ export type RadioButtonProps = Omit<React.HTMLProps<HTMLInputElement>, 'size' | | |||||
* Short complementary content displayed at the edge of the component. | * Short complementary content displayed at the edge of the component. | ||||
*/ | */ | ||||
badge?: React.ReactNode, | badge?: React.ReactNode, | ||||
/** | |||||
* Appearance of the component. | |||||
*/ | |||||
appearance?: CheckControlBase.CheckControlAppearance, | |||||
} | } | ||||
/** | /** | ||||
@@ -54,12 +46,10 @@ export const RadioButton = React.forwardRef<HTMLInputElement, RadioButtonProps>( | |||||
border = false, | border = false, | ||||
children, | children, | ||||
block = false, | block = false, | ||||
style = ButtonBase.ButtonStyle.DEFAULT, | |||||
disabled = false, | disabled = false, | ||||
compact = false, | compact = false, | ||||
subtext, | subtext, | ||||
badge, | badge, | ||||
appearance = CheckControlBase.CheckControlAppearance.DEFAULT, | |||||
className: _className, | className: _className, | ||||
as: _as, | as: _as, | ||||
...etcProps | ...etcProps | ||||
@@ -71,23 +61,12 @@ export const RadioButton = React.forwardRef<HTMLInputElement, RadioButtonProps>( | |||||
block, | block, | ||||
variant, | variant, | ||||
border, | border, | ||||
style, | |||||
compact, | compact, | ||||
menuItem: false, | menuItem: false, | ||||
disabled, | disabled, | ||||
appearance, | |||||
appearance: CheckControlBase.CheckControlAppearance.BUTTON, | |||||
type: 'radio', | type: 'radio', | ||||
}), [size, block, variant, border, style, compact, disabled, appearance]); | |||||
const checkIndicatorCommon = ( | |||||
<span | |||||
className={CheckControlBase.CheckIndicatorArea(styleProps)} | |||||
> | |||||
<span | |||||
className={CheckControlBase.CheckIndicatorWrapper(styleProps)} | |||||
/> | |||||
</span> | |||||
) | |||||
}), [size, block, variant, border, compact, disabled]); | |||||
return ( | return ( | ||||
<div | <div | ||||
@@ -103,144 +82,63 @@ export const RadioButton = React.forwardRef<HTMLInputElement, RadioButtonProps>( | |||||
ref={ref} | ref={ref} | ||||
className={CheckControlBase.CheckStateContainer(styleProps)} | className={CheckControlBase.CheckStateContainer(styleProps)} | ||||
/> | /> | ||||
{ | |||||
appearance === CheckControlBase.CheckControlAppearance.DEFAULT | |||||
&& ( | |||||
<span> | |||||
<span /> | |||||
{checkIndicatorCommon} | |||||
<span> | |||||
{children} | |||||
{ | |||||
subtext | |||||
&& ( | |||||
<> | |||||
<br /> | |||||
<span | |||||
className={CheckControlBase.Subtext()} | |||||
> | |||||
{subtext} | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
{ | |||||
badge | |||||
&& ( | |||||
<> | |||||
{' '} | |||||
<span | |||||
className={ButtonBase.BadgeContainer(styleProps)} | |||||
> | |||||
{badge} | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
) | |||||
} | |||||
{ | |||||
appearance === CheckControlBase.CheckControlAppearance.BUTTON | |||||
&& ( | |||||
<span | |||||
className={ButtonBase.Button(styleProps)} | |||||
> | |||||
<span | |||||
className={ButtonBase.Border(styleProps)} | |||||
/> | |||||
<span | |||||
className={CheckControlBase.CheckIndicatorArea(styleProps)} | |||||
> | |||||
<span | <span | ||||
className={ButtonBase.Button(styleProps)} | |||||
className={CheckControlBase.CheckIndicatorWrapper(styleProps)} | |||||
/> | |||||
</span> | |||||
<span | |||||
className={ButtonBase.Label(styleProps)} | |||||
> | |||||
<span | |||||
className={ButtonBase.MainText()} | |||||
> | > | ||||
<span | <span | ||||
className={ButtonBase.Border(styleProps)} | |||||
/> | |||||
{checkIndicatorCommon} | |||||
<span | |||||
className={ButtonBase.Label(styleProps)} | |||||
className={ButtonBase.OverflowText()} | |||||
> | > | ||||
<span | |||||
className={ButtonBase.MainText()} | |||||
> | |||||
{children} | |||||
</span> | |||||
</span> | |||||
{ | |||||
subtext | |||||
&& ( | |||||
<> | |||||
{' '} | |||||
<span | <span | ||||
className={ButtonBase.OverflowText()} | |||||
className={ButtonBase.Subtext()} | |||||
> | > | ||||
{children} | |||||
</span> | |||||
</span> | |||||
{ | |||||
subtext | |||||
&& ( | |||||
<> | |||||
{' '} | |||||
<span | |||||
className={ButtonBase.Subtext()} | |||||
> | |||||
<span | |||||
className={ButtonBase.OverflowText()} | |||||
> | |||||
{subtext} | |||||
</span> | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
{ | |||||
badge | |||||
&& ( | |||||
<> | |||||
{' '} | |||||
<span | <span | ||||
className={ButtonBase.BadgeContainer(styleProps)} | |||||
className={ButtonBase.OverflowText()} | |||||
> | > | ||||
{badge} | |||||
{subtext} | |||||
</span> | </span> | ||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
) | |||||
} | |||||
{ | |||||
appearance === CheckControlBase.CheckControlAppearance.SWITCH | |||||
&& ( | |||||
<span> | |||||
<span /> | |||||
<span | |||||
className={CheckControlBase.CheckIndicatorArea(styleProps)} | |||||
> | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
{ | |||||
badge | |||||
&& ( | |||||
<> | |||||
{' '} | |||||
<span | <span | ||||
className={CheckControlBase.CheckIndicatorWrapper(styleProps)} | |||||
/> | |||||
</span> | |||||
<span> | |||||
{children} | |||||
{ | |||||
subtext | |||||
&& ( | |||||
<> | |||||
<br /> | |||||
<span | |||||
className={CheckControlBase.Subtext()} | |||||
> | |||||
{subtext} | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
{ | |||||
badge | |||||
&& ( | |||||
<> | |||||
{' '} | |||||
<span | |||||
className={ButtonBase.BadgeContainer(styleProps)} | |||||
> | |||||
{badge} | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
) | |||||
} | |||||
className={ButtonBase.BadgeContainer(styleProps)} | |||||
> | |||||
{badge} | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
</label> | </label> | ||||
</div> | </div> | ||||
); | ); | ||||
@@ -0,0 +1,20 @@ | |||||
import * as React from 'react'; | |||||
import { | |||||
render, | |||||
screen | |||||
} from '@testing-library/react'; | |||||
import '@testing-library/jest-dom'; | |||||
import * as CheckControlBase from '@tesseract-design/web-base-checkcontrol'; | |||||
import { RadioTickBox } from '.'; | |||||
jest.mock('@tesseract-design/web-base-checkcontrol'); | |||||
describe('RadioTickBox', () => { | |||||
it('should render a radio button', () => { | |||||
render( | |||||
<RadioTickBox /> | |||||
); | |||||
const checkbox = screen.getByRole('radio'); | |||||
expect(checkbox).toBeInTheDocument(); | |||||
}); | |||||
}); |
@@ -0,0 +1,89 @@ | |||||
import * as React from 'react'; | |||||
import * as CheckControlBase from '@tesseract-design/web-base-checkcontrol'; | |||||
export type RadioTickBoxProps = Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'type' | 'style'> & { | |||||
/** | |||||
* Should the component occupy the whole width of its parent? | |||||
*/ | |||||
block?: boolean, | |||||
/** | |||||
* Does the component need to conserve space? | |||||
*/ | |||||
compact?: boolean, | |||||
/** | |||||
* Complementary content of the component. | |||||
*/ | |||||
subtext?: React.ReactNode, | |||||
} | |||||
/** | |||||
* Component for performing an action upon activation (e.g. when clicked). | |||||
* | |||||
* This component functions as a regular button. | |||||
*/ | |||||
export const RadioTickBox = React.forwardRef<HTMLInputElement, RadioTickBoxProps>( | |||||
( | |||||
{ | |||||
children, | |||||
block = false, | |||||
compact = false, | |||||
subtext, | |||||
className: _className, | |||||
as: _as, | |||||
...etcProps | |||||
}: RadioTickBoxProps, | |||||
ref, | |||||
) => { | |||||
const styleProps = React.useMemo<CheckControlBase.CheckControlBaseArgs>(() => ({ | |||||
block, | |||||
compact, | |||||
appearance: CheckControlBase.CheckControlAppearance.TICK_BOX, | |||||
type: 'radio', | |||||
}), [block, compact]); | |||||
return ( | |||||
<div | |||||
className={CheckControlBase.ClickAreaWrapper(styleProps)} | |||||
> | |||||
<label | |||||
className={CheckControlBase.ClickArea()} | |||||
> | |||||
<input | |||||
{...etcProps} | |||||
type="radio" | |||||
ref={ref} | |||||
className={CheckControlBase.CheckStateContainer(styleProps)} | |||||
/> | |||||
<span> | |||||
<span /> | |||||
<span | |||||
className={CheckControlBase.CheckIndicatorArea(styleProps)} | |||||
> | |||||
<span | |||||
className={CheckControlBase.CheckIndicatorWrapper(styleProps)} | |||||
/> | |||||
</span> | |||||
<span> | |||||
{children} | |||||
{ | |||||
subtext | |||||
&& ( | |||||
<> | |||||
<br /> | |||||
<span | |||||
className={CheckControlBase.Subtext()} | |||||
> | |||||
{subtext} | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
</span> | |||||
</label> | |||||
</div> | |||||
); | |||||
}, | |||||
); | |||||
RadioTickBox.displayName = 'ActionButton'; |
@@ -0,0 +1,32 @@ | |||||
import * as React from 'react'; | |||||
import { | |||||
render, | |||||
screen | |||||
} from '@testing-library/react'; | |||||
import '@testing-library/jest-dom'; | |||||
import * as ButtonBase from '@tesseract-design/web-base-button'; | |||||
import * as CheckControlBase from '@tesseract-design/web-base-checkcontrol'; | |||||
import { ToggleButton } from '.'; | |||||
jest.mock('@tesseract-design/web-base-button'); | |||||
jest.mock('@tesseract-design/web-base-checkcontrol'); | |||||
describe('ToggleButton', () => { | |||||
it('should render a checkbox', () => { | |||||
render( | |||||
<ToggleButton /> | |||||
); | |||||
const checkbox = screen.getByRole('checkbox'); | |||||
expect(checkbox).toBeInTheDocument(); | |||||
}); | |||||
it('should render an indeterminate checkbox', () => { | |||||
render( | |||||
<ToggleButton | |||||
indeterminate | |||||
/> | |||||
); | |||||
const checkbox = screen.getByRole('checkbox'); | |||||
expect(checkbox).toHaveProperty('indeterminate', true); | |||||
}); | |||||
}); |
@@ -0,0 +1,181 @@ | |||||
import * as React from 'react'; | |||||
import * as ButtonBase from '@tesseract-design/web-base-button'; | |||||
import * as CheckControlBase from '@tesseract-design/web-base-checkcontrol'; | |||||
export type ToggleButtonProps = Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'type' | 'style'> & { | |||||
/** | |||||
* Size of the component. | |||||
*/ | |||||
size?: ButtonBase.ButtonSize, | |||||
/** | |||||
* Variant of the component. | |||||
*/ | |||||
variant?: ButtonBase.ButtonVariant, | |||||
/** | |||||
* Should the component display a border? | |||||
*/ | |||||
border?: boolean, | |||||
/** | |||||
* Should the component occupy the whole width of its parent? | |||||
*/ | |||||
block?: boolean, | |||||
/** | |||||
* Does the component need to conserve space? | |||||
*/ | |||||
compact?: boolean, | |||||
/** | |||||
* Complementary content of the component. | |||||
*/ | |||||
subtext?: React.ReactNode, | |||||
/** | |||||
* Short complementary content displayed at the edge of the component. | |||||
*/ | |||||
badge?: React.ReactNode, | |||||
/** | |||||
* Does the component have indeterminate check state? | |||||
*/ | |||||
indeterminate?: boolean, | |||||
} | |||||
/** | |||||
* Component for performing an action upon activation (e.g. when clicked). | |||||
* | |||||
* This component functions as a regular button. | |||||
*/ | |||||
export const ToggleButton = React.forwardRef<HTMLInputElement, ToggleButtonProps>( | |||||
( | |||||
{ | |||||
size = ButtonBase.ButtonSize.MEDIUM, | |||||
variant = ButtonBase.ButtonVariant.OUTLINE, | |||||
border = false, | |||||
children, | |||||
block = false, | |||||
disabled = false, | |||||
compact = false, | |||||
subtext, | |||||
badge, | |||||
indeterminate = false, | |||||
className: _className, | |||||
as: _as, | |||||
...etcProps | |||||
}: ToggleButtonProps, | |||||
ref, | |||||
) => { | |||||
const styleProps = React.useMemo<ButtonBase.ButtonBaseArgs & CheckControlBase.CheckControlBaseArgs>(() => ({ | |||||
size, | |||||
block, | |||||
variant, | |||||
border, | |||||
compact, | |||||
menuItem: false, | |||||
disabled, | |||||
appearance: CheckControlBase.CheckControlAppearance.BUTTON, | |||||
type: 'checkbox', | |||||
}), [size, block, variant, border, compact, disabled]); | |||||
const defaultRef = React.useRef<HTMLInputElement>(null); | |||||
const theRef = (ref ?? defaultRef) as React.MutableRefObject<HTMLInputElement>; | |||||
React.useEffect(() => { | |||||
if (!(indeterminate && theRef.current)) { | |||||
return; | |||||
} | |||||
theRef.current.indeterminate = indeterminate; | |||||
}, [theRef, indeterminate]); | |||||
return ( | |||||
<div | |||||
className={CheckControlBase.ClickAreaWrapper(styleProps)} | |||||
> | |||||
<label | |||||
className={CheckControlBase.ClickArea()} | |||||
> | |||||
<input | |||||
{...etcProps} | |||||
disabled={disabled} | |||||
type="checkbox" | |||||
ref={theRef} | |||||
className={CheckControlBase.CheckStateContainer(styleProps)} | |||||
/> | |||||
<span | |||||
className={ButtonBase.Button(styleProps)} | |||||
> | |||||
<span | |||||
className={ButtonBase.Border(styleProps)} | |||||
/> | |||||
<span | |||||
className={CheckControlBase.CheckIndicatorArea(styleProps)} | |||||
> | |||||
<span | |||||
className={CheckControlBase.CheckIndicatorWrapper(styleProps)} | |||||
> | |||||
<svg | |||||
className={CheckControlBase.CheckIndicator(styleProps)} | |||||
viewBox="0 0 24 24" | |||||
role="presentation" | |||||
> | |||||
<polyline | |||||
points="20 6 9 17 4 12" | |||||
/> | |||||
</svg> | |||||
<svg | |||||
className={CheckControlBase.CheckIndicator(styleProps)} | |||||
viewBox="0 0 24 24" | |||||
role="presentation" | |||||
> | |||||
<polyline | |||||
points="20 12 4 12" | |||||
/> | |||||
</svg> | |||||
</span> | |||||
</span> | |||||
<span | |||||
className={ButtonBase.Label(styleProps)} | |||||
> | |||||
<span | |||||
className={ButtonBase.MainText()} | |||||
> | |||||
<span | |||||
className={ButtonBase.OverflowText()} | |||||
> | |||||
{children} | |||||
</span> | |||||
</span> | |||||
{ | |||||
subtext | |||||
&& ( | |||||
<> | |||||
{' '} | |||||
<span | |||||
className={ButtonBase.Subtext()} | |||||
> | |||||
<span | |||||
className={ButtonBase.OverflowText()} | |||||
> | |||||
{subtext} | |||||
</span> | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
{ | |||||
badge | |||||
&& ( | |||||
<> | |||||
{' '} | |||||
<span | |||||
className={ButtonBase.BadgeContainer(styleProps)} | |||||
> | |||||
{badge} | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
</label> | |||||
</div> | |||||
); | |||||
}, | |||||
); | |||||
ToggleButton.displayName = 'ActionButton'; |
@@ -0,0 +1,20 @@ | |||||
import * as React from 'react'; | |||||
import { | |||||
render, | |||||
screen | |||||
} from '@testing-library/react'; | |||||
import '@testing-library/jest-dom'; | |||||
import * as CheckControlBase from '@tesseract-design/web-base-checkcontrol'; | |||||
import { ToggleSwitch } from '.'; | |||||
jest.mock('@tesseract-design/web-base-checkcontrol'); | |||||
describe('ToggleSwitch', () => { | |||||
it('should render a checkbox', () => { | |||||
render( | |||||
<ToggleSwitch /> | |||||
); | |||||
const checkbox = screen.getByRole('checkbox'); | |||||
expect(checkbox).toBeInTheDocument(); | |||||
}); | |||||
}); |
@@ -0,0 +1,109 @@ | |||||
import * as React from 'react'; | |||||
import * as CheckControlBase from '@tesseract-design/web-base-checkcontrol'; | |||||
export type ToggleSwitchProps = Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'type' | 'style'> & { | |||||
/** | |||||
* Should the component occupy the whole width of its parent? | |||||
*/ | |||||
block?: boolean, | |||||
/** | |||||
* Does the component need to conserve space? | |||||
*/ | |||||
compact?: boolean, | |||||
/** | |||||
* Complementary content of the component. | |||||
*/ | |||||
subtext?: React.ReactNode, | |||||
} | |||||
/** | |||||
* Component for performing an action upon activation (e.g. when clicked). | |||||
* | |||||
* This component functions as a regular button. | |||||
*/ | |||||
export const ToggleSwitch = React.forwardRef<HTMLInputElement, ToggleSwitchProps>( | |||||
( | |||||
{ | |||||
children, | |||||
block = false, | |||||
compact = false, | |||||
subtext, | |||||
className: _className, | |||||
as: _as, | |||||
...etcProps | |||||
}: ToggleSwitchProps, | |||||
ref, | |||||
) => { | |||||
const styleProps = React.useMemo<CheckControlBase.CheckControlBaseArgs>(() => ({ | |||||
block, | |||||
compact, | |||||
menuItem: false, | |||||
appearance: CheckControlBase.CheckControlAppearance.SWITCH, | |||||
type: 'checkbox', | |||||
}), [block, compact]); | |||||
return ( | |||||
<div | |||||
className={CheckControlBase.ClickAreaWrapper(styleProps)} | |||||
> | |||||
<label | |||||
className={CheckControlBase.ClickArea()} | |||||
> | |||||
<input | |||||
{...etcProps} | |||||
type="checkbox" | |||||
ref={ref} | |||||
className={CheckControlBase.CheckStateContainer(styleProps)} | |||||
/> | |||||
<span> | |||||
<span /> | |||||
<span | |||||
className={CheckControlBase.CheckIndicatorArea(styleProps)} | |||||
> | |||||
<span | |||||
className={CheckControlBase.CheckIndicatorWrapper(styleProps)} | |||||
> | |||||
<svg | |||||
className={CheckControlBase.CheckIndicator(styleProps)} | |||||
viewBox="0 0 24 24" | |||||
role="presentation" | |||||
> | |||||
<polyline | |||||
points="20 6 9 17 4 12" | |||||
/> | |||||
</svg> | |||||
<svg | |||||
className={CheckControlBase.CheckIndicator(styleProps)} | |||||
viewBox="0 0 24 24" | |||||
role="presentation" | |||||
> | |||||
<polyline | |||||
points="20 12 4 12" | |||||
/> | |||||
</svg> | |||||
</span> | |||||
</span> | |||||
<span> | |||||
{children} | |||||
{ | |||||
subtext | |||||
&& ( | |||||
<> | |||||
<br /> | |||||
<span | |||||
className={CheckControlBase.Subtext()} | |||||
> | |||||
{subtext} | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
</span> | |||||
</label> | |||||
</div> | |||||
); | |||||
}, | |||||
); | |||||
ToggleSwitch.displayName = 'ActionButton'; |
@@ -0,0 +1,30 @@ | |||||
import * as React from 'react'; | |||||
import { | |||||
render, | |||||
screen | |||||
} from '@testing-library/react'; | |||||
import '@testing-library/jest-dom'; | |||||
import * as CheckControlBase from '@tesseract-design/web-base-checkcontrol'; | |||||
import { ToggleTickBox } from '.'; | |||||
jest.mock('@tesseract-design/web-base-checkcontrol'); | |||||
describe('ToggleTickBox', () => { | |||||
it('should render a checkbox', () => { | |||||
render( | |||||
<ToggleTickBox /> | |||||
); | |||||
const checkbox = screen.getByRole('checkbox'); | |||||
expect(checkbox).toBeInTheDocument(); | |||||
}); | |||||
it('should render an indeterminate checkbox', () => { | |||||
render( | |||||
<ToggleTickBox | |||||
indeterminate | |||||
/> | |||||
); | |||||
const checkbox = screen.getByRole('checkbox'); | |||||
expect(checkbox).toHaveProperty('indeterminate', true); | |||||
}); | |||||
}); |
@@ -0,0 +1,122 @@ | |||||
import * as React from 'react'; | |||||
import * as CheckControlBase from '@tesseract-design/web-base-checkcontrol'; | |||||
export type ToggleTickBoxProps = Omit<React.HTMLProps<HTMLInputElement>, 'type' | 'style'> & { | |||||
/** | |||||
* Should the component occupy the whole width of its parent? | |||||
*/ | |||||
block?: boolean, | |||||
/** | |||||
* Does the component need to conserve space? | |||||
*/ | |||||
compact?: boolean, | |||||
/** | |||||
* Complementary content of the component. | |||||
*/ | |||||
subtext?: React.ReactNode, | |||||
/** | |||||
* Does the component have indeterminate check state? | |||||
*/ | |||||
indeterminate?: boolean, | |||||
} | |||||
/** | |||||
* Component for performing an action upon activation (e.g. when clicked). | |||||
* | |||||
* This component functions as a regular button. | |||||
*/ | |||||
export const ToggleTickBox = React.forwardRef<HTMLInputElement, ToggleTickBoxProps>( | |||||
( | |||||
{ | |||||
children, | |||||
block = false, | |||||
compact = false, | |||||
subtext, | |||||
indeterminate = false, | |||||
className: _className, | |||||
as: _as, | |||||
...etcProps | |||||
}: ToggleTickBoxProps, | |||||
ref, | |||||
) => { | |||||
const styleProps = React.useMemo<CheckControlBase.CheckControlBaseArgs>(() => ({ | |||||
block, | |||||
compact, | |||||
appearance: CheckControlBase.CheckControlAppearance.TICK_BOX, | |||||
type: 'checkbox', | |||||
}), [block, compact]); | |||||
const defaultRef = React.useRef<HTMLInputElement>(null); | |||||
const theRef = (ref ?? defaultRef) as React.MutableRefObject<HTMLInputElement>; | |||||
React.useEffect(() => { | |||||
if (!(indeterminate && theRef.current)) { | |||||
return; | |||||
} | |||||
theRef.current.indeterminate = indeterminate; | |||||
}, [theRef, indeterminate]); | |||||
return ( | |||||
<div | |||||
className={CheckControlBase.ClickAreaWrapper(styleProps)} | |||||
> | |||||
<label | |||||
className={CheckControlBase.ClickArea()} | |||||
> | |||||
<input | |||||
{...etcProps} | |||||
type="checkbox" | |||||
ref={theRef} | |||||
className={CheckControlBase.CheckStateContainer(styleProps)} | |||||
/> | |||||
<span> | |||||
<span /> | |||||
<span | |||||
className={CheckControlBase.CheckIndicatorArea(styleProps)} | |||||
> | |||||
<span | |||||
className={CheckControlBase.CheckIndicatorWrapper(styleProps)} | |||||
> | |||||
<svg | |||||
className={CheckControlBase.CheckIndicator(styleProps)} | |||||
viewBox="0 0 24 24" | |||||
role="presentation" | |||||
> | |||||
<polyline | |||||
points="20 6 9 17 4 12" | |||||
/> | |||||
</svg> | |||||
<svg | |||||
className={CheckControlBase.CheckIndicator(styleProps)} | |||||
viewBox="0 0 24 24" | |||||
role="presentation" | |||||
> | |||||
<polyline | |||||
points="20 12 4 12" | |||||
/> | |||||
</svg> | |||||
</span> | |||||
</span> | |||||
<span> | |||||
{children} | |||||
{ | |||||
subtext | |||||
&& ( | |||||
<> | |||||
<br /> | |||||
<span | |||||
className={CheckControlBase.Subtext()} | |||||
> | |||||
{subtext} | |||||
</span> | |||||
</> | |||||
) | |||||
} | |||||
</span> | |||||
</span> | |||||
</label> | |||||
</div> | |||||
); | |||||
}, | |||||
); | |||||
ToggleTickBox.displayName = 'ActionButton'; |
@@ -1,3 +1,6 @@ | |||||
export * from './components/Checkbox'; | |||||
export * from './components/DropdownSelect'; | export * from './components/DropdownSelect'; | ||||
export * from './components/RadioButton'; | export * from './components/RadioButton'; | ||||
export * from './components/RadioTickBox'; | |||||
export * from './components/ToggleButton'; | |||||
export * from './components/ToggleSwitch'; | |||||
export * from './components/ToggleTickBox'; |
@@ -0,0 +1,14 @@ | |||||
import * as WebOptionReact from '.'; | |||||
describe('web-option-react', () => { | |||||
it.each([ | |||||
'DropdownSelect', | |||||
'RadioButton', | |||||
'RadioTickBox', | |||||
'ToggleButton', | |||||
'ToggleSwitch', | |||||
'ToggleTickBox', | |||||
])('should export %s', (namedExport) => { | |||||
expect(WebOptionReact).toHaveProperty(namedExport); | |||||
}); | |||||
}); |
@@ -1,7 +1,7 @@ | |||||
import { NextPage } from 'next'; | import { NextPage } from 'next'; | ||||
import { ButtonSize, ButtonVariant } from '@tesseract-design/web-base-button'; | import { ButtonSize, ButtonVariant } from '@tesseract-design/web-base-button'; | ||||
import * as Navigation from '@tesseract-design/web-navigation-react'; | import * as Navigation from '@tesseract-design/web-navigation-react'; | ||||
import * as Info from '@tesseract-design/web-info-react'; | |||||
import * as Info from '@tesseract-design/web-information-react'; | |||||
const ActionPage: NextPage = () => { | const ActionPage: NextPage = () => { | ||||
return ( | return ( | ||||
@@ -614,21 +614,21 @@ const OptionPage: NextPage<Props> = ({ | |||||
<div> | <div> | ||||
<div className="grid md:grid-cols-2 gap-4 my-4"> | <div className="grid md:grid-cols-2 gap-4 my-4"> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
subtext="Subtext" | subtext="Subtext" | ||||
> | > | ||||
Checkbox | Checkbox | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
indeterminate | indeterminate | ||||
subtext="Subtext" | subtext="Subtext" | ||||
> | > | ||||
Checkbox | Checkbox | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -638,23 +638,23 @@ const OptionPage: NextPage<Props> = ({ | |||||
<div> | <div> | ||||
<div className="grid md:grid-cols-2 gap-4 my-4"> | <div className="grid md:grid-cols-2 gap-4 my-4"> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
subtext="Subtext" | subtext="Subtext" | ||||
appearance={CheckControlAppearance.SWITCH} | appearance={CheckControlAppearance.SWITCH} | ||||
> | > | ||||
Checkbox | Checkbox | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
indeterminate | indeterminate | ||||
subtext="Subtext" | subtext="Subtext" | ||||
appearance={CheckControlAppearance.SWITCH} | appearance={CheckControlAppearance.SWITCH} | ||||
> | > | ||||
Checkbox | Checkbox | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -664,72 +664,72 @@ const OptionPage: NextPage<Props> = ({ | |||||
<div> | <div> | ||||
<div className="grid md:grid-cols-2 gap-4 my-4"> | <div className="grid md:grid-cols-2 gap-4 my-4"> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
appearance={CheckControlAppearance.BUTTON} | appearance={CheckControlAppearance.BUTTON} | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
variant={ButtonVariant.FILLED} | variant={ButtonVariant.FILLED} | ||||
block | block | ||||
appearance={CheckControlAppearance.BUTTON} | appearance={CheckControlAppearance.BUTTON} | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
border | border | ||||
block | block | ||||
appearance={CheckControlAppearance.BUTTON} | appearance={CheckControlAppearance.BUTTON} | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
border | border | ||||
variant={ButtonVariant.FILLED} | variant={ButtonVariant.FILLED} | ||||
block | block | ||||
appearance={CheckControlAppearance.BUTTON} | appearance={CheckControlAppearance.BUTTON} | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
disabled | disabled | ||||
appearance={CheckControlAppearance.BUTTON} | appearance={CheckControlAppearance.BUTTON} | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
variant={ButtonVariant.FILLED} | variant={ButtonVariant.FILLED} | ||||
block | block | ||||
disabled | disabled | ||||
appearance={CheckControlAppearance.BUTTON} | appearance={CheckControlAppearance.BUTTON} | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
border | border | ||||
block | block | ||||
disabled | disabled | ||||
appearance={CheckControlAppearance.BUTTON} | appearance={CheckControlAppearance.BUTTON} | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
border | border | ||||
variant={ButtonVariant.FILLED} | variant={ButtonVariant.FILLED} | ||||
block | block | ||||
@@ -737,20 +737,20 @@ const OptionPage: NextPage<Props> = ({ | |||||
appearance={CheckControlAppearance.BUTTON} | appearance={CheckControlAppearance.BUTTON} | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
border | border | ||||
block | block | ||||
appearance={CheckControlAppearance.BUTTON} | appearance={CheckControlAppearance.BUTTON} | ||||
indeterminate | indeterminate | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
border | border | ||||
variant={ButtonVariant.FILLED} | variant={ButtonVariant.FILLED} | ||||
block | block | ||||
@@ -758,7 +758,7 @@ const OptionPage: NextPage<Props> = ({ | |||||
indeterminate | indeterminate | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -768,17 +768,17 @@ const OptionPage: NextPage<Props> = ({ | |||||
<div> | <div> | ||||
<div className="grid md:grid-cols-2 gap-4 my-4"> | <div className="grid md:grid-cols-2 gap-4 my-4"> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
border | border | ||||
size={ButtonSize.SMALL} | size={ButtonSize.SMALL} | ||||
appearance={CheckControlAppearance.BUTTON} | appearance={CheckControlAppearance.BUTTON} | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
border | border | ||||
variant={ButtonVariant.FILLED} | variant={ButtonVariant.FILLED} | ||||
@@ -786,20 +786,20 @@ const OptionPage: NextPage<Props> = ({ | |||||
appearance={CheckControlAppearance.BUTTON} | appearance={CheckControlAppearance.BUTTON} | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
border | border | ||||
size={ButtonSize.MEDIUM} | size={ButtonSize.MEDIUM} | ||||
appearance={CheckControlAppearance.BUTTON} | appearance={CheckControlAppearance.BUTTON} | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
border | border | ||||
variant={ButtonVariant.FILLED} | variant={ButtonVariant.FILLED} | ||||
@@ -807,20 +807,20 @@ const OptionPage: NextPage<Props> = ({ | |||||
appearance={CheckControlAppearance.BUTTON} | appearance={CheckControlAppearance.BUTTON} | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
border | border | ||||
size={ButtonSize.LARGE} | size={ButtonSize.LARGE} | ||||
appearance={CheckControlAppearance.BUTTON} | appearance={CheckControlAppearance.BUTTON} | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
border | border | ||||
variant={ButtonVariant.FILLED} | variant={ButtonVariant.FILLED} | ||||
@@ -828,7 +828,7 @@ const OptionPage: NextPage<Props> = ({ | |||||
appearance={CheckControlAppearance.BUTTON} | appearance={CheckControlAppearance.BUTTON} | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -838,17 +838,17 @@ const OptionPage: NextPage<Props> = ({ | |||||
<div> | <div> | ||||
<div className="grid md:grid-cols-2 gap-4 my-4"> | <div className="grid md:grid-cols-2 gap-4 my-4"> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
compact | compact | ||||
border | border | ||||
appearance={CheckControlAppearance.BUTTON} | appearance={CheckControlAppearance.BUTTON} | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
compact | compact | ||||
border | border | ||||
@@ -856,10 +856,10 @@ const OptionPage: NextPage<Props> = ({ | |||||
appearance={CheckControlAppearance.BUTTON} | appearance={CheckControlAppearance.BUTTON} | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
compact | compact | ||||
border | border | ||||
@@ -871,10 +871,10 @@ const OptionPage: NextPage<Props> = ({ | |||||
} | } | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
compact | compact | ||||
border | border | ||||
@@ -887,10 +887,10 @@ const OptionPage: NextPage<Props> = ({ | |||||
} | } | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
compact | compact | ||||
border | border | ||||
@@ -903,10 +903,10 @@ const OptionPage: NextPage<Props> = ({ | |||||
} | } | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<Option.Checkbox | |||||
<Option.ToggleTickBox | |||||
block | block | ||||
compact | compact | ||||
border | border | ||||
@@ -920,7 +920,7 @@ const OptionPage: NextPage<Props> = ({ | |||||
} | } | ||||
> | > | ||||
Button | Button | ||||
</Option.Checkbox> | |||||
</Option.ToggleTickBox> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -17,9 +17,10 @@ | |||||
"paths": { | "paths": { | ||||
"@tesseract-design/web-action-react": ["./src/modules/action"], | "@tesseract-design/web-action-react": ["./src/modules/action"], | ||||
"@tesseract-design/web-freeform-react": ["./src/modules/freeform"], | "@tesseract-design/web-freeform-react": ["./src/modules/freeform"], | ||||
"@tesseract-design/web-info-react": ["./src/modules/info"], | |||||
"@tesseract-design/web-information-react": ["./src/modules/information"], | |||||
"@tesseract-design/web-navigation-react": ["./src/modules/navigation"], | "@tesseract-design/web-navigation-react": ["./src/modules/navigation"], | ||||
"@tesseract-design/web-option-react": ["./src/modules/option"], | "@tesseract-design/web-option-react": ["./src/modules/option"], | ||||
"@tesseract-design/web-base-badge": ["./src/modules/base-badge"], | |||||
"@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-textcontrol": ["./src/modules/base-textcontrol"], | "@tesseract-design/web-base-textcontrol": ["./src/modules/base-textcontrol"], | ||||
@@ -1,3 +1,6 @@ | |||||
{ | { | ||||
"extends": "./tsconfig.json" | |||||
"extends": "./tsconfig.json", | |||||
"compilerOptions": { | |||||
"jsx": "react" | |||||
} | |||||
} | } |
@@ -317,6 +317,13 @@ | |||||
dependencies: | dependencies: | ||||
regenerator-runtime "^0.13.4" | regenerator-runtime "^0.13.4" | ||||
"@babel/runtime@^7.12.5": | |||||
version "7.16.3" | |||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5" | |||||
integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ== | |||||
dependencies: | |||||
regenerator-runtime "^0.13.4" | |||||
"@babel/template@^7.16.0", "@babel/template@^7.3.3": | "@babel/template@^7.16.0", "@babel/template@^7.3.3": | ||||
version "7.16.0" | version "7.16.0" | ||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6" | resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6" | ||||
@@ -707,6 +714,13 @@ | |||||
dependencies: | dependencies: | ||||
"@sinonjs/commons" "^1.7.0" | "@sinonjs/commons" "^1.7.0" | ||||
"@testing-library/user-event@^13.5.0": | |||||
version "13.5.0" | |||||
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.5.0.tgz#69d77007f1e124d55314a2b73fd204b333b13295" | |||||
integrity sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg== | |||||
dependencies: | |||||
"@babel/runtime" "^7.12.5" | |||||
"@tootallnate/once@1": | "@tootallnate/once@1": | ||||
version "1.1.2" | version "1.1.2" | ||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" | resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" | ||||