{
it('should render a textbox', () => {
- render(
);
+ render(
+
+ );
const textbox = screen.getByRole('textbox');
expect(textbox).toBeInTheDocument();
+ expect(textbox).toHaveProperty('type', 'text');
+ });
+
+ it('should render a border', () => {
+ render(
+
+ );
+ const border = screen.getByTestId('border');
+ expect(border).toBeInTheDocument();
+ });
+
+ it('should render a label', () => {
+ render(
+
+ );
+ const textbox = screen.getByLabelText('foo');
+ expect(textbox).toBeInTheDocument();
+ const label = screen.getByTestId('label');
+ expect(label).toHaveTextContent('foo');
+ });
+
+ it('should render a hidden label', () => {
+ render(
+
+ );
+ const textbox = screen.getByLabelText('foo');
+ expect(textbox).toBeInTheDocument();
+ const label = screen.queryByTestId('label');
+ expect(label).toBeNull();
+ });
+
+ it('should render a hint', () => {
+ render(
+
+ );
+ const hint = screen.getByTestId('hint');
+ expect(hint).toBeInTheDocument();
+ });
+
+ it('should render an indicator', () => {
+ render(
+
+ }
+ />
+ );
+ 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(
+
+ );
+
+ expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({
+ size,
+ }));
+ });
+
+ it('should render hint styles', () => {
+ render(
+
+ );
+
+ expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({
+ size,
+ }));
+ });
+
+ it('should render indicator styles', () => {
+ render(
+
+ }
+ />
+ );
+
+ expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({
+ size,
+ }));
+ });
+ });
+
+ it('should render a block textbox', () => {
+ render(
+
+ );
+
+ expect(TextControlBase.Root).toBeCalledWith(expect.objectContaining({
+ block: true,
+ }));
+ });
+
+ it.each([
+ TextInputType.TEXT,
+ TextInputType.SEARCH,
+ ])('should render a textbox with type %s', (buttonType) => {
+ render(
+
+ );
+ 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(
+
+ );
+
+ expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({
+ style,
+ }));
+ });
+
+ it('should render hint styles', () => {
+ render(
+
+ );
+
+ expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({
+ style,
+ }));
+ });
+
+ it('should render indicator styles', () => {
+ render(
+
+ }
+ />
+ );
+
+ expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({
+ style,
+ }));
+ });
+ });
+
+ it('should handle change events', () => {
+ const onChange = jest.fn();
+ render(
+
+ );
+ const textbox: HTMLInputElement = screen.getByRole('textbox');
+ userEvent.type(textbox, 'foobar');
+ expect(onChange).toBeCalled();
});
});
diff --git a/src/modules/freeform/components/TextInput/index.tsx b/src/modules/freeform/components/TextInput/index.tsx
index cc19e64..f2e12b4 100644
--- a/src/modules/freeform/components/TextInput/index.tsx
+++ b/src/modules/freeform/components/TextInput/index.tsx
@@ -81,7 +81,7 @@ export const TextInput = React.forwardRef
(
return (
(
ref={ref}
aria-label={label}
type={type}
+ data-testid="input"
/>
- {border &&
}
+ {
+ border && (
+
+ )
+ }
{
label && !hiddenLabel && (
{label}
@@ -103,6 +111,7 @@ export const TextInput = React.forwardRef
(
{hint && (
{
+ it.each([
+ 'MaskedTextInput',
+ 'MultilineTextInput',
+ 'TextInput',
+ ])('should export %s', (namedExport) => {
+ expect(WebFreeformReact).toHaveProperty(namedExport);
+ });
+});
diff --git a/src/modules/info/components/Badge/index.tsx b/src/modules/info/components/Badge/index.tsx
deleted file mode 100644
index 4cbc855..0000000
--- a/src/modules/info/components/Badge/index.tsx
+++ /dev/null
@@ -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
& {
- rounded?: boolean,
-};
-
-export const Badge = React.forwardRef(
- (
- {
- children,
- rounded = false,
- },
- ref,
- ) => {
- const badgeStyleProps = React.useMemo(() => ({
- rounded,
- }), [rounded]);
-
- return (
-
-
- {children}
-
-
- )
- }
-)
-
-Badge.displayName = 'Badge';
diff --git a/src/modules/information/components/Badge/Badge.test.tsx b/src/modules/information/components/Badge/Badge.test.tsx
new file mode 100644
index 0000000..dbee982
--- /dev/null
+++ b/src/modules/information/components/Badge/Badge.test.tsx
@@ -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(
+
+ );
+ const button: HTMLButtonElement = screen.getByTestId('badge');
+ expect(button).toBeInTheDocument();
+ });
+
+ it('should render a rounded badge', () => {
+ render(
+
+ );
+ const button: HTMLButtonElement = screen.getByTestId('badge');
+ expect(button).toBeInTheDocument();
+ });
+});
diff --git a/src/modules/information/components/Badge/index.tsx b/src/modules/information/components/Badge/index.tsx
new file mode 100644
index 0000000..786d8ad
--- /dev/null
+++ b/src/modules/information/components/Badge/index.tsx
@@ -0,0 +1,36 @@
+import * as React from 'react';
+import * as BadgeBase from '@tesseract-design/web-base-badge';
+
+export type BadgeProps = React.HTMLProps & {
+ rounded?: boolean,
+};
+
+export const Badge = React.forwardRef(
+ (
+ {
+ children,
+ rounded = false,
+ },
+ ref,
+ ) => {
+ const badgeStyleProps = React.useMemo(() => ({
+ rounded,
+ }), [rounded]);
+
+ return (
+
+
+ {children}
+
+
+ )
+ }
+)
+
+Badge.displayName = 'Badge';
diff --git a/src/modules/info/index.ts b/src/modules/information/index.ts
similarity index 100%
rename from src/modules/info/index.ts
rename to src/modules/information/index.ts
diff --git a/src/modules/information/web-information-react.test.ts b/src/modules/information/web-information-react.test.ts
new file mode 100644
index 0000000..d62106a
--- /dev/null
+++ b/src/modules/information/web-information-react.test.ts
@@ -0,0 +1,9 @@
+import * as WebInformationReact from '.';
+
+describe('web-information-react', () => {
+ it.each([
+ 'Badge',
+ ])('should export %s', (namedExport) => {
+ expect(WebInformationReact).toHaveProperty(namedExport);
+ });
+});
diff --git a/src/modules/navigation/components/LinkButton/LinkButton.test.tsx b/src/modules/navigation/components/LinkButton/LinkButton.test.tsx
new file mode 100644
index 0000000..d035039
--- /dev/null
+++ b/src/modules/navigation/components/LinkButton/LinkButton.test.tsx
@@ -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(
+
+ );
+ const button: HTMLButtonElement = screen.getByRole('link');
+ expect(button).toBeInTheDocument();
+ });
+
+ it('should render a subtext', () => {
+ render(
+
+ );
+ const subtext: HTMLElement = screen.getByTestId('subtext');
+ expect(subtext).toBeInTheDocument();
+ });
+
+ it('should render a badge', () => {
+ render(
+
+ );
+ const badge: HTMLElement = screen.getByTestId('badge');
+ expect(badge).toBeInTheDocument();
+ });
+
+ it('should render as a menu item', () => {
+ render(
+
+ );
+ const menuItemIndicator: HTMLElement = screen.getByTestId('menuItemIndicator');
+ expect(menuItemIndicator).toBeInTheDocument();
+ });
+
+ it('should handle click events', () => {
+ const onClick = jest.fn();
+ render(
+
+ );
+ const button: HTMLButtonElement = screen.getByRole('link');
+ userEvent.click(button);
+ expect(onClick).toBeCalled();
+ });
+
+ it('should render a compact button', () => {
+ render(
+
+ );
+
+ 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(
+
+ );
+
+ expect(ButtonBase.Button).toBeCalledWith(expect.objectContaining({
+ size,
+ }));
+ });
+
+ it('should render badge styles', () => {
+ render(
+
+ );
+
+ expect(ButtonBase.BadgeContainer).toBeCalledWith(expect.objectContaining({
+ size,
+ }));
+ });
+
+ it('should render indicator styles', () => {
+ render(
+
+ );
+
+ expect(ButtonBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({
+ size,
+ }));
+ });
+ });
+
+ it.each([
+ ButtonBase.ButtonVariant.OUTLINE,
+ ButtonBase.ButtonVariant.FILLED,
+ ])('should render a button with variant %s', (variant) => {
+ render(
+
+ );
+
+ expect(ButtonBase.Button).toBeCalledWith(expect.objectContaining({
+ variant,
+ }));
+ });
+
+ it('should render a bordered button', () => {
+ render(
+
+ );
+
+ expect(ButtonBase.Border).toBeCalledWith(expect.objectContaining({
+ border: true,
+ }));
+ });
+
+ it('should render a block button', () => {
+ render(
+
+ );
+
+ expect(ButtonBase.Border).toBeCalledWith(expect.objectContaining({
+ block: true,
+ }));
+ });
+
+ it('should render children', () => {
+ render(
+
+ Foo
+
+ );
+
+ const children: HTMLElement = screen.getByTestId('children');
+ expect(children).toHaveTextContent('Foo');
+ });
+
+ it('should render a disabled link', () => {
+ render(
+
+ );
+ const button = screen.queryByRole('link');
+ expect(button).toBeNull();
+ });
+});
diff --git a/src/modules/navigation/components/LinkButton/index.tsx b/src/modules/navigation/components/LinkButton/index.tsx
index 5c6eb1d..be40a68 100644
--- a/src/modules/navigation/components/LinkButton/index.tsx
+++ b/src/modules/navigation/components/LinkButton/index.tsx
@@ -20,14 +20,6 @@ export type LinkButtonProps = Omit, 'size' |
* Should the component occupy the whole width of its parent?
*/
block?: boolean,
- /**
- * Can the component be activated?
- */
- disabled?: boolean,
- /**
- * Style of the component.
- */
- style?: ButtonBase.ButtonStyle,
/**
* Does the component need to conserve space?
*/
@@ -60,7 +52,6 @@ export const LinkButton = React.forwardRef(
children,
block = false,
disabled = false,
- style = ButtonBase.ButtonStyle.DEFAULT,
onClick,
href,
target,
@@ -81,10 +72,9 @@ export const LinkButton = React.forwardRef(
variant,
border,
disabled,
- style,
compact,
menuItem,
- }), [size, block, variant, border, disabled, style, compact, menuItem]);
+ }), [size, block, variant, border, disabled, compact, menuItem]);
const commonChildren = (
<>
@@ -96,6 +86,7 @@ export const LinkButton = React.forwardRef(
>
(
{' '}
(
{' '}
{badge}
@@ -141,6 +134,7 @@ export const LinkButton = React.forwardRef(
{' '}
{
+ it.each([
+ 'LinkButton',
+ ])('should export %s', (namedExport) => {
+ expect(WebNavigationReact).toHaveProperty(namedExport);
+ });
+});
diff --git a/src/modules/option/components/Checkbox/index.tsx b/src/modules/option/components/Checkbox/index.tsx
deleted file mode 100644
index 293a9a8..0000000
--- a/src/modules/option/components/Checkbox/index.tsx
+++ /dev/null
@@ -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, '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(
- (
- {
- 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(() => ({
- size,
- block,
- variant,
- border,
- style,
- compact,
- menuItem: false,
- disabled,
- appearance,
- type: 'checkbox',
- }), [size, block, variant, border, style, compact, disabled, appearance]);
- const defaultRef = React.useRef(null);
- const theRef = (ref ?? defaultRef) as React.MutableRefObject;
-
- React.useEffect(() => {
- if (!(indeterminate && theRef.current)) {
- return;
- }
- theRef.current.indeterminate = indeterminate;
- }, [theRef, indeterminate]);
-
- const checkIndicatorCommon = (
-
-
-
-
-
-
-
-
-
-
- )
-
- return (
-
-
-
- {
- appearance === CheckControlBase.CheckControlAppearance.DEFAULT
- && (
-
-
- {checkIndicatorCommon}
-
- {children}
- {
- subtext
- && (
- <>
-
-
- {subtext}
-
- >
- )
- }
-
- {
- badge
- && (
- <>
- {' '}
-
- {badge}
-
- >
- )
- }
-
- )
- }
- {
- appearance === CheckControlBase.CheckControlAppearance.BUTTON
- && (
-
-
- {checkIndicatorCommon}
-
-
-
- {children}
-
-
- {
- subtext
- && (
- <>
- {' '}
-
-
- {subtext}
-
-
- >
- )
- }
-
- {
- badge
- && (
- <>
- {' '}
-
- {badge}
-
- >
- )
- }
-
- )
- }
- {
- appearance === CheckControlBase.CheckControlAppearance.SWITCH
- && (
-
-
- {checkIndicatorCommon}
-
- {children}
- {
- subtext
- && (
- <>
-
-
- {subtext}
-
- >
- )
- }
-
- {
- badge
- && (
- <>
- {' '}
-
- {badge}
-
- >
- )
- }
-
- )
- }
-
-
- );
- },
-);
-
-Checkbox.displayName = 'ActionButton';
diff --git a/src/modules/option/components/DropdownSelect/DropdownSelect.test.tsx b/src/modules/option/components/DropdownSelect/DropdownSelect.test.tsx
index 1417e6c..ccfb136 100644
--- a/src/modules/option/components/DropdownSelect/DropdownSelect.test.tsx
+++ b/src/modules/option/components/DropdownSelect/DropdownSelect.test.tsx
@@ -4,14 +4,279 @@ import {
screen
} from '@testing-library/react';
import '@testing-library/jest-dom';
+import * as TextControlBase from '@tesseract-design/web-base-textcontrol';
import {
DropdownSelect
} from '.';
+jest.mock('@tesseract-design/web-base-textcontrol');
+
describe('DropdownSelect', () => {
it('should render a combobox', () => {
render( );
const combobox = screen.getByRole('combobox');
expect(combobox).toBeInTheDocument();
});
+
+
+ it('should render a border', () => {
+ render(
+
+ );
+ const border = screen.getByTestId('border');
+ expect(border).toBeInTheDocument();
+ });
+
+ it('should render a label', () => {
+ render(
+
+ );
+ const combobox = screen.getByLabelText('foo');
+ expect(combobox).toBeInTheDocument();
+ const label = screen.getByTestId('label');
+ expect(label).toHaveTextContent('foo');
+ });
+
+ it('should render a hidden label', () => {
+ render(
+
+ );
+ const combobox = screen.getByLabelText('foo');
+ expect(combobox).toBeInTheDocument();
+ const label = screen.queryByTestId('label');
+ expect(label).toBeNull();
+ });
+
+ it('should render a hint', () => {
+ render(
+
+ );
+ const hint = screen.getByTestId('hint');
+ expect(hint).toBeInTheDocument();
+ });
+
+ it('should not render invalid options', () => {
+ render(
+
+ );
+ const combobox = screen.getByRole('combobox');
+ expect(combobox.children).toHaveLength(0);
+ });
+
+ it('should render valid options', () => {
+ render(
+
+ );
+ const combobox = screen.getByRole('combobox');
+ expect(combobox.children).toHaveLength(2);
+ });
+
+ it('should render shallow option groups', () => {
+ render(
+
+ );
+ 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(
+
+ );
+ 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(
+
+ );
+
+ expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({
+ size,
+ }));
+ });
+
+ it('should render hint styles', () => {
+ render(
+
+ );
+
+ expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({
+ size,
+ }));
+ });
+
+ it('should render indicator styles', () => {
+ render(
+
+ );
+
+ expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({
+ size,
+ }));
+ });
+ });
+
+ it('should render a block textbox', () => {
+ render(
+
+ );
+
+ 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(
+
+ );
+
+ expect(TextControlBase.Input).toBeCalledWith(expect.objectContaining({
+ style,
+ }));
+ });
+
+ it('should render hint styles', () => {
+ render(
+
+ );
+
+ expect(TextControlBase.HintWrapper).toBeCalledWith(expect.objectContaining({
+ style,
+ }));
+ });
+
+ it('should render indicator styles', () => {
+ render(
+
+ );
+
+ expect(TextControlBase.IndicatorWrapper).toBeCalledWith(expect.objectContaining({
+ style,
+ }));
+ });
+ });
});
diff --git a/src/modules/option/components/DropdownSelect/index.tsx b/src/modules/option/components/DropdownSelect/index.tsx
index 3486d0f..a547ab3 100644
--- a/src/modules/option/components/DropdownSelect/index.tsx
+++ b/src/modules/option/components/DropdownSelect/index.tsx
@@ -1,19 +1,6 @@
import * as React from 'react';
-import {
- css
-} from 'goober';
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 {
label: string,
value?: string | number | readonly string[]
@@ -22,12 +9,8 @@ export interface SelectOption {
type RenderOptionsProps = {
options: SelectOption[],
- // FIXME bug in eslint does not play well with React.VFC
- // eslint-disable-next-line react/require-default-props
optionComponent?: React.ElementType,
- // eslint-disable-next-line react/require-default-props
optgroupComponent?: React.ElementType,
- // eslint-disable-next-line react/require-default-props
level?: number,
}
@@ -62,6 +45,7 @@ const RenderOptions: React.VFC = ({
options={o.children}
optionComponent={Option}
optgroupComponent={Optgroup}
+ level={level + 1}
/>
);
@@ -79,6 +63,7 @@ const RenderOptions: React.VFC = ({
options={o.children}
optionComponent={Option}
optgroupComponent={Optgroup}
+ level={level + 1}
/>
);
@@ -123,10 +108,6 @@ export type DropdownSelectProps = Omit, 'size
* Options available for the component's values.
*/
options?: SelectOption[],
- /**
- * Component for rendering options.
- */
- renderOptions?: React.ElementType,
}
/**
@@ -149,7 +130,6 @@ export const DropdownSelect = React.forwardRef
-
- {border && }
+ {border && (
+
+ )}
{label && !hiddenLabel && (
{label}
@@ -189,6 +174,7 @@ export const DropdownSelect = React.forwardRef
diff --git a/src/modules/option/components/RadioButton/RadioButton.test.tsx b/src/modules/option/components/RadioButton/RadioButton.test.tsx
new file mode 100644
index 0000000..6e4b1ab
--- /dev/null
+++ b/src/modules/option/components/RadioButton/RadioButton.test.tsx
@@ -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(
+
+ );
+ const checkbox = screen.getByRole('radio');
+ expect(checkbox).toBeInTheDocument();
+ });
+});
diff --git a/src/modules/option/components/RadioButton/index.tsx b/src/modules/option/components/RadioButton/index.tsx
index 5e2f2d1..5cf4bc0 100644
--- a/src/modules/option/components/RadioButton/index.tsx
+++ b/src/modules/option/components/RadioButton/index.tsx
@@ -19,10 +19,6 @@ export type RadioButtonProps = Omit, 'size' |
* 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?
*/
@@ -35,10 +31,6 @@ export type RadioButtonProps = Omit, 'size' |
* Short complementary content displayed at the edge of the component.
*/
badge?: React.ReactNode,
- /**
- * Appearance of the component.
- */
- appearance?: CheckControlBase.CheckControlAppearance,
}
/**
@@ -54,12 +46,10 @@ export const RadioButton = React.forwardRef(
border = false,
children,
block = false,
- style = ButtonBase.ButtonStyle.DEFAULT,
disabled = false,
compact = false,
subtext,
badge,
- appearance = CheckControlBase.CheckControlAppearance.DEFAULT,
className: _className,
as: _as,
...etcProps
@@ -71,23 +61,12 @@ export const RadioButton = React.forwardRef(
block,
variant,
border,
- style,
compact,
menuItem: false,
disabled,
- appearance,
+ appearance: CheckControlBase.CheckControlAppearance.BUTTON,
type: 'radio',
- }), [size, block, variant, border, style, compact, disabled, appearance]);
-
- const checkIndicatorCommon = (
-
-
-
- )
+ }), [size, block, variant, border, compact, disabled]);
return (
(
ref={ref}
className={CheckControlBase.CheckStateContainer(styleProps)}
/>
- {
- appearance === CheckControlBase.CheckControlAppearance.DEFAULT
- && (
-
-
- {checkIndicatorCommon}
-
- {children}
- {
- subtext
- && (
- <>
-
-
- {subtext}
-
- >
- )
- }
-
- {
- badge
- && (
- <>
- {' '}
-
- {badge}
-
- >
- )
- }
-
- )
- }
- {
- appearance === CheckControlBase.CheckControlAppearance.BUTTON
- && (
+
+
+
+
+
+
- {checkIndicatorCommon}
-
-
+ {children}
+
+
+ {
+ subtext
+ && (
+ <>
+ {' '}
- {children}
-
-
- {
- subtext
- && (
- <>
- {' '}
-
-
- {subtext}
-
-
- >
- )
- }
-
- {
- badge
- && (
- <>
- {' '}
- {badge}
+ {subtext}
- >
- )
- }
-
- )
- }
- {
- appearance === CheckControlBase.CheckControlAppearance.SWITCH
- && (
-
-
-
+
+ >
+ )
+ }
+
+ {
+ badge
+ && (
+ <>
+ {' '}
-
-
- {children}
- {
- subtext
- && (
- <>
-
-
- {subtext}
-
- >
- )
- }
-
- {
- badge
- && (
- <>
- {' '}
-
- {badge}
-
- >
- )
- }
-
- )
- }
+ className={ButtonBase.BadgeContainer(styleProps)}
+ >
+ {badge}
+
+ >
+ )
+ }
+
);
diff --git a/src/modules/option/components/RadioTickBox/RadioTickBox.test.tsx b/src/modules/option/components/RadioTickBox/RadioTickBox.test.tsx
new file mode 100644
index 0000000..6469ea1
--- /dev/null
+++ b/src/modules/option/components/RadioTickBox/RadioTickBox.test.tsx
@@ -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(
+
+ );
+ const checkbox = screen.getByRole('radio');
+ expect(checkbox).toBeInTheDocument();
+ });
+});
diff --git a/src/modules/option/components/RadioTickBox/index.tsx b/src/modules/option/components/RadioTickBox/index.tsx
new file mode 100644
index 0000000..fd4d677
--- /dev/null
+++ b/src/modules/option/components/RadioTickBox/index.tsx
@@ -0,0 +1,89 @@
+import * as React from 'react';
+import * as CheckControlBase from '@tesseract-design/web-base-checkcontrol';
+
+export type RadioTickBoxProps = Omit, '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(
+ (
+ {
+ children,
+ block = false,
+ compact = false,
+ subtext,
+ className: _className,
+ as: _as,
+ ...etcProps
+ }: RadioTickBoxProps,
+ ref,
+ ) => {
+ const styleProps = React.useMemo(() => ({
+ block,
+ compact,
+ appearance: CheckControlBase.CheckControlAppearance.TICK_BOX,
+ type: 'radio',
+ }), [block, compact]);
+
+ return (
+
+
+
+
+
+
+
+
+
+ {children}
+ {
+ subtext
+ && (
+ <>
+
+
+ {subtext}
+
+ >
+ )
+ }
+
+
+
+
+ );
+ },
+);
+
+RadioTickBox.displayName = 'ActionButton';
diff --git a/src/modules/option/components/ToggleButton/ToggleButton.test.tsx b/src/modules/option/components/ToggleButton/ToggleButton.test.tsx
new file mode 100644
index 0000000..766daae
--- /dev/null
+++ b/src/modules/option/components/ToggleButton/ToggleButton.test.tsx
@@ -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(
+
+ );
+ const checkbox = screen.getByRole('checkbox');
+ expect(checkbox).toBeInTheDocument();
+ });
+
+ it('should render an indeterminate checkbox', () => {
+ render(
+
+ );
+ const checkbox = screen.getByRole('checkbox');
+ expect(checkbox).toHaveProperty('indeterminate', true);
+ });
+});
diff --git a/src/modules/option/components/ToggleButton/index.tsx b/src/modules/option/components/ToggleButton/index.tsx
new file mode 100644
index 0000000..269627c
--- /dev/null
+++ b/src/modules/option/components/ToggleButton/index.tsx
@@ -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, '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(
+ (
+ {
+ 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(() => ({
+ size,
+ block,
+ variant,
+ border,
+ compact,
+ menuItem: false,
+ disabled,
+ appearance: CheckControlBase.CheckControlAppearance.BUTTON,
+ type: 'checkbox',
+ }), [size, block, variant, border, compact, disabled]);
+ const defaultRef = React.useRef(null);
+ const theRef = (ref ?? defaultRef) as React.MutableRefObject;
+
+ React.useEffect(() => {
+ if (!(indeterminate && theRef.current)) {
+ return;
+ }
+ theRef.current.indeterminate = indeterminate;
+ }, [theRef, indeterminate]);
+
+ return (
+
+ );
+ },
+);
+
+ToggleButton.displayName = 'ActionButton';
diff --git a/src/modules/option/components/ToggleSwitch/ToggleSwitch.test.tsx b/src/modules/option/components/ToggleSwitch/ToggleSwitch.test.tsx
new file mode 100644
index 0000000..acc4ac8
--- /dev/null
+++ b/src/modules/option/components/ToggleSwitch/ToggleSwitch.test.tsx
@@ -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(
+
+ );
+ const checkbox = screen.getByRole('checkbox');
+ expect(checkbox).toBeInTheDocument();
+ });
+});
diff --git a/src/modules/option/components/ToggleSwitch/index.tsx b/src/modules/option/components/ToggleSwitch/index.tsx
new file mode 100644
index 0000000..5d43131
--- /dev/null
+++ b/src/modules/option/components/ToggleSwitch/index.tsx
@@ -0,0 +1,109 @@
+import * as React from 'react';
+import * as CheckControlBase from '@tesseract-design/web-base-checkcontrol';
+
+export type ToggleSwitchProps = Omit, '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(
+ (
+ {
+ children,
+ block = false,
+ compact = false,
+ subtext,
+ className: _className,
+ as: _as,
+ ...etcProps
+ }: ToggleSwitchProps,
+ ref,
+ ) => {
+ const styleProps = React.useMemo(() => ({
+ block,
+ compact,
+ menuItem: false,
+ appearance: CheckControlBase.CheckControlAppearance.SWITCH,
+ type: 'checkbox',
+ }), [block, compact]);
+
+ return (
+
+ );
+ },
+);
+
+ToggleSwitch.displayName = 'ActionButton';
diff --git a/src/modules/option/components/ToggleTickBox/ToggleTickBox.test.tsx b/src/modules/option/components/ToggleTickBox/ToggleTickBox.test.tsx
new file mode 100644
index 0000000..9bc506d
--- /dev/null
+++ b/src/modules/option/components/ToggleTickBox/ToggleTickBox.test.tsx
@@ -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(
+
+ );
+ const checkbox = screen.getByRole('checkbox');
+ expect(checkbox).toBeInTheDocument();
+ });
+
+ it('should render an indeterminate checkbox', () => {
+ render(
+
+ );
+ const checkbox = screen.getByRole('checkbox');
+ expect(checkbox).toHaveProperty('indeterminate', true);
+ });
+});
diff --git a/src/modules/option/components/ToggleTickBox/index.tsx b/src/modules/option/components/ToggleTickBox/index.tsx
new file mode 100644
index 0000000..37110e2
--- /dev/null
+++ b/src/modules/option/components/ToggleTickBox/index.tsx
@@ -0,0 +1,122 @@
+import * as React from 'react';
+import * as CheckControlBase from '@tesseract-design/web-base-checkcontrol';
+
+export type ToggleTickBoxProps = Omit, '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(
+ (
+ {
+ children,
+ block = false,
+ compact = false,
+ subtext,
+ indeterminate = false,
+ className: _className,
+ as: _as,
+ ...etcProps
+ }: ToggleTickBoxProps,
+ ref,
+ ) => {
+ const styleProps = React.useMemo(() => ({
+ block,
+ compact,
+ appearance: CheckControlBase.CheckControlAppearance.TICK_BOX,
+ type: 'checkbox',
+ }), [block, compact]);
+ const defaultRef = React.useRef(null);
+ const theRef = (ref ?? defaultRef) as React.MutableRefObject;
+
+ React.useEffect(() => {
+ if (!(indeterminate && theRef.current)) {
+ return;
+ }
+ theRef.current.indeterminate = indeterminate;
+ }, [theRef, indeterminate]);
+
+ return (
+
+ );
+ },
+);
+
+ToggleTickBox.displayName = 'ActionButton';
diff --git a/src/modules/option/index.ts b/src/modules/option/index.ts
index 5e59f5d..27c507f 100644
--- a/src/modules/option/index.ts
+++ b/src/modules/option/index.ts
@@ -1,3 +1,6 @@
-export * from './components/Checkbox';
export * from './components/DropdownSelect';
export * from './components/RadioButton';
+export * from './components/RadioTickBox';
+export * from './components/ToggleButton';
+export * from './components/ToggleSwitch';
+export * from './components/ToggleTickBox';
diff --git a/src/modules/option/web-option-react.test.ts b/src/modules/option/web-option-react.test.ts
new file mode 100644
index 0000000..c4a2ecc
--- /dev/null
+++ b/src/modules/option/web-option-react.test.ts
@@ -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);
+ });
+});
diff --git a/src/pages/categories/navigation/index.tsx b/src/pages/categories/navigation/index.tsx
index b16dae7..b38c041 100644
--- a/src/pages/categories/navigation/index.tsx
+++ b/src/pages/categories/navigation/index.tsx
@@ -1,7 +1,7 @@
import { NextPage } from 'next';
import { ButtonSize, ButtonVariant } from '@tesseract-design/web-base-button';
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 = () => {
return (
diff --git a/src/pages/categories/option/index.tsx b/src/pages/categories/option/index.tsx
index 964889a..ce40b63 100644
--- a/src/pages/categories/option/index.tsx
+++ b/src/pages/categories/option/index.tsx
@@ -614,21 +614,21 @@ const OptionPage: NextPage = ({
-
Checkbox
-
+
-
Checkbox
-
+
@@ -638,23 +638,23 @@ const OptionPage: NextPage = ({
-
Checkbox
-
+
-
Checkbox
-
+
@@ -664,72 +664,72 @@ const OptionPage: NextPage = ({
-
Button
-
+
-
Button
-
+
-
Button
-
+
-
Button
-
+
-
Button
-
+
-
Button
-
+
-
Button
-
+
- = ({
appearance={CheckControlAppearance.BUTTON}
>
Button
-
+
-
Button
-
+
- = ({
indeterminate
>
Button
-
+
@@ -768,17 +768,17 @@ const OptionPage: NextPage = ({
-
Button
-
+
- = ({
appearance={CheckControlAppearance.BUTTON}
>
Button
-
+
-
Button
-
+
- = ({
appearance={CheckControlAppearance.BUTTON}
>
Button
-
+
-
Button
-
+
- = ({
appearance={CheckControlAppearance.BUTTON}
>
Button
-
+
@@ -838,17 +838,17 @@ const OptionPage: NextPage = ({
-
Button
-
+
- = ({
appearance={CheckControlAppearance.BUTTON}
>
Button
-
+
- = ({
}
>
Button
-
+
- = ({
}
>
Button
-
+
- = ({
}
>
Button
-
+
- = ({
}
>
Button
-
+
diff --git a/tsconfig.json b/tsconfig.json
index 423179a..9fc649e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -17,9 +17,10 @@
"paths": {
"@tesseract-design/web-action-react": ["./src/modules/action"],
"@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-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-checkcontrol": ["./src/modules/base-checkcontrol"],
"@tesseract-design/web-base-textcontrol": ["./src/modules/base-textcontrol"],
diff --git a/tsconfig.test.json b/tsconfig.test.json
index fc8520e..ec83611 100644
--- a/tsconfig.test.json
+++ b/tsconfig.test.json
@@ -1,3 +1,6 @@
{
- "extends": "./tsconfig.json"
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react"
+ }
}
diff --git a/yarn.lock b/yarn.lock
index cc75bcd..4389ada 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -317,6 +317,13 @@
dependencies:
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":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6"
@@ -707,6 +714,13 @@
dependencies:
"@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":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"