Переглянути джерело

Update documentation

Start migrating tests, tsdoc across components.
master
TheoryOfNekomata 1 рік тому
джерело
коміт
88e754bcee
14 змінених файлів з 646 додано та 381 видалено
  1. +5
    -1
      TODO.md
  2. +1
    -0
      categories/action/react/.gitignore
  3. +3
    -2
      categories/action/react/package.json
  4. +4
    -0
      categories/action/react/setupTests.ts
  5. +186
    -0
      categories/action/react/src/components/ActionButton/ActionButton.test.tsx
  6. +46
    -10
      categories/action/react/src/components/ActionButton/index.tsx
  7. +10
    -0
      categories/action/react/src/index.test.ts
  8. +17
    -11
      categories/choice/react/src/components/ComboBox/index.tsx
  9. +145
    -143
      categories/choice/react/src/components/DropdownSelect/index.tsx
  10. +161
    -159
      categories/choice/react/src/components/MenuSelect/index.tsx
  11. +36
    -7
      categories/choice/react/src/components/RadioButton/index.tsx
  12. +20
    -3
      categories/choice/react/src/components/RadioTickBox/index.tsx
  13. +12
    -0
      pnpm-lock.yaml
  14. +0
    -45
      showcases/web-kitchensink-reactnext/src/pages/categories/code/index.tsx

+ 5
- 1
TODO.md Переглянути файл

@@ -13,6 +13,10 @@
- Color
- [ ] ColorPicker
- [X] Swatch
- Code
- [ ] CodeInput (extract to own package)
- [X] CodeBlock (`react-refractor`)
- [ ] VerifyCodeInput (for OTP inputs)
- Formatted
- [X] EmailInput
- [X] PhoneNumberInput
@@ -61,4 +65,4 @@
- [ ] YearInput

# Others
- [ ] Add `select-none` to input labels, etc.
- [X] Add `select-none` to input labels, etc.

+ 1
- 0
categories/action/react/.gitignore Переглянути файл

@@ -105,3 +105,4 @@ dist
.tern-port

.npmrc
types/

+ 3
- 2
categories/action/react/package.json Переглянути файл

@@ -15,6 +15,7 @@
"devDependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"@types/node": "^18.14.1",
"@types/react": "^18.0.27",
"eslint": "^8.35.0",
@@ -58,8 +59,8 @@
"access": "public"
},
"dependencies": {
"clsx": "^1.2.1",
"@tesseract-design/web-base": "workspace:*"
"@tesseract-design/web-base": "workspace:*",
"clsx": "^1.2.1"
},
"types": "./dist/types/index.d.ts",
"main": "./dist/cjs/production/index.js",


+ 4
- 0
categories/action/react/setupTests.ts Переглянути файл

@@ -0,0 +1,4 @@
import matchers from '@testing-library/jest-dom/matchers';
import { expect } from 'vitest';

expect.extend(matchers);

+ 186
- 0
categories/action/react/src/components/ActionButton/ActionButton.test.tsx Переглянути файл

@@ -0,0 +1,186 @@
import * as React from 'react';
import {
render,
screen,
cleanup,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Button } from '@tesseract-design/web-base';
import {
vi,
describe,
it,
expect, afterEach,
} from 'vitest';
import {
ActionButton,
} from '.';
import matchers from '@testing-library/jest-dom/matchers';
expect.extend(matchers);

vi.mock('@tesseract-design/web-base-button');

describe('ActionButton', () => {
afterEach(() => {
cleanup();
});

it('renders a button', () => {
render(
<ActionButton />,
);
const button: HTMLButtonElement = screen.getByRole('button');
expect(button).toBeInTheDocument();
expect(button).toHaveProperty('type', 'button');
});

it('renders a subtext', () => {
render(
<ActionButton
subtext="subtext"
/>,
);
const subtext: HTMLElement = screen.getByTestId('subtext');
expect(subtext).toBeInTheDocument();
});

it('renders a badge', () => {
render(
<ActionButton
badge="badge"
/>,
);
const badge: HTMLElement = screen.getByTestId('badge');
expect(badge).toBeInTheDocument();
});

it('renders as a menu item', () => {
render(
<ActionButton
menuItem
/>,
);
const menuItemIndicator: HTMLElement = screen.getByTestId('menuItemIndicator');
expect(menuItemIndicator).toBeInTheDocument();
});

it('handles click events', async () => {
const onClick = vi.fn().mockImplementationOnce((e) => { e.preventDefault() });
render(
<ActionButton
onClick={onClick}
/>,
);
const button: HTMLButtonElement = screen.getByRole('button');
await userEvent.click(button);
expect(onClick).toBeCalled();
});

it('renders a compact button', () => {
render(
<ActionButton
compact
/>,
);

const button: HTMLButtonElement = screen.getByRole('button');
expect(button).toHaveClass('pl-2');
expect(button).toHaveClass('gap-2');
expect(button).toHaveClass('pr-2');
});

describe.each`
size | className
${'small'} | ${'h-10'}
${'medium'} | ${'h-12'}
${'large'} | ${'h-16'}
`('on %s size', ({
size,
className,
}: { size: Button.Size, className: string }) => {
it('renders button styles', () => {
render(
<ActionButton
size={size}
/>,
);

const button: HTMLButtonElement = screen.getByRole('button');
expect(button).toHaveClass(className);
});

it('renders badge styles', () => {
render(
<ActionButton
size={size}
badge="badge"
/>,
);

const badge = screen.getByTestId('badge');
expect(badge).toBeInTheDocument();
});
});

it.each`
variant | className
${'bare'} | ${'bg-negative'}
${'outline'} | ${'border-2'}
${'filled'} | ${'bg-primary'}
`('renders a button with variant %s', ({
variant,
className,
}: { variant: Button.Variant, className: string }) => {
render(
<ActionButton
variant={variant}
/>,
);

const button: HTMLButtonElement = screen.getByRole('button');
expect(button).toHaveClass(className);
});

it('renders a block button', () => {
render(
<ActionButton
block
/>,
);

const button: HTMLButtonElement = screen.getByRole('button');
expect(button).toHaveClass('w-full');
expect(button).toHaveClass('flex');
});

it('renders children', () => {
render(
<ActionButton>
Foo
</ActionButton>
);

const children: HTMLElement = screen.getByTestId('children');
expect(children).toHaveTextContent('Foo');
});

it.each(['button', 'submit'] as const)('renders a button with type %s', (buttonType) => {
render(
<ActionButton
type={buttonType}
/>
);
const button: HTMLButtonElement = screen.getByRole('button');
expect(button).toHaveProperty('type', buttonType);
});

it('renders a disabled button', () => {
render(
<ActionButton
disabled
/>
);
const button: HTMLButtonElement = screen.getByTestId('button');
expect(button).toBeDisabled();
});
});

+ 46
- 10
categories/action/react/src/components/ActionButton/index.tsx Переглянути файл

@@ -2,37 +2,73 @@ import * as React from 'react';
import clsx from 'clsx';
import { Button } from '@tesseract-design/web-base';

/**
* Derived HTML element of the {@link ActionButton} component.
*/
export type ActionButtonDerivedElement = HTMLButtonElement;

/**
* Props of the {@link ActionButton} component.
*/
export interface ActionButtonProps extends Omit<React.HTMLProps<ActionButtonDerivedElement>, 'type' | 'size'> {
/**
* Type of the component.
*/
type?: Button.Type;
/**
* Variant of the component.
*/
variant?: Button.Variant;
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean;
/**
* Complementary content of the component.
*/
subtext?: React.ReactNode;
/**
* Short complementary content displayed at the edge of the component.
*/
badge?: React.ReactNode;
/**
* Is this component part of a menu?
*/
menuItem?: boolean;
/**
* Size of the component.
*/
size?: Button.Size;
/**
* Should the component's content use minimal space?
*/
compact?: boolean;
}

/**
* Component for performing an action upon activation (e.g. when clicked).
*
* This component functions as a regular button.
*/
export const ActionButton = React.forwardRef<ActionButtonDerivedElement, ActionButtonProps>((
{
type = 'button' as const,
variant = 'bare' as const,
subtext,
badge,
menuItem = false,
menuItem = false as const,
children,
size = 'medium' as const,
compact = false,
compact = false as const,
className,
block = false,
block = false as const,
...etcProps
},
forwardedRef,
) => (
<button
{...etcProps}
data-testid="button"
type={type}
ref={forwardedRef}
className={clsx(
@@ -120,15 +156,15 @@ export const ActionButton = React.forwardRef<ActionButtonDerivedElement, ActionB
</button>
));

ActionButton.displayName = 'ActionButton';
ActionButton.displayName = 'ActionButton' as const;

ActionButton.defaultProps = {
type: 'button',
variant: 'bare',
block: false,
type: 'button' as const,
variant: 'bare' as const,
block: false as const,
subtext: undefined,
badge: undefined,
menuItem: false,
size: 'medium',
compact: false,
menuItem: false as const,
size: 'medium' as const,
compact: false as const,
};

+ 10
- 0
categories/action/react/src/index.test.ts Переглянути файл

@@ -0,0 +1,10 @@
import { describe, it, expect } from 'vitest';
import * as WebActionReact from '.';

describe('web-action-react', () => {
it.each([
'ActionButton',
])('exports %s', (namedExport) => {
expect(WebActionReact).toHaveProperty(namedExport);
});
});

+ 17
- 11
categories/choice/react/src/components/ComboBox/index.tsx Переглянути файл

@@ -2,8 +2,14 @@ import * as React from 'react';
import { TextControl } from '@tesseract-design/web-base';
import clsx from 'clsx';

/**
* Derived HTML element of the {@link ComboBox} component.
*/
export type ComboBoxDerivedElement = HTMLInputElement;

/**
* Props of the {@link ComboBox} component.
*/
export interface ComboBoxProps extends Omit<React.HTMLProps<ComboBoxDerivedElement>, 'size' | 'type' | 'style' | 'label' | 'list' | 'inputMode'> {
/**
* Short textual description indicating the nature of the component's value.
@@ -58,11 +64,11 @@ export const ComboBox = React.forwardRef<ComboBoxDerivedElement, ComboBoxProps>(
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
border = false as const,
block = false as const,
type = 'text' as const,
variant = 'default' as const,
hiddenLabel = false,
hiddenLabel = false as const,
className,
children,
inputMode = 'text' as const,
@@ -226,17 +232,17 @@ export const ComboBox = React.forwardRef<ComboBoxDerivedElement, ComboBoxProps>(
);
});

ComboBox.displayName = 'ComboBox';
ComboBox.displayName = 'ComboBox' as const;

ComboBox.defaultProps = {
label: undefined,
hint: undefined,
indicator: undefined,
size: 'medium',
border: false,
block: false,
type: 'text',
variant: 'default',
hiddenLabel: false,
inputMode: 'text',
size: 'medium' as const,
border: false as const,
block: false as const,
type: 'text' as const,
variant: 'default' as const,
hiddenLabel: false as const,
inputMode: 'text' as const,
};

+ 145
- 143
categories/choice/react/src/components/DropdownSelect/index.tsx Переглянути файл

@@ -2,8 +2,14 @@ import * as React from 'react';
import { TextControl } from '@tesseract-design/web-base';
import clsx from 'clsx';

/**
* Derived HTML element of the {@link DropdownSelect} component.
*/
export type DropdownSelectDerivedElement = HTMLSelectElement;

/**
* Props of the {@link DropdownSelect} component.
*/
export interface DropdownSelectProps extends Omit<React.HTMLProps<DropdownSelectDerivedElement>, 'size' | 'type' | 'style' | 'label' | 'list' | 'multiple'> {
/**
* Short textual description indicating the nature of the component's value.
@@ -40,177 +46,173 @@ export interface DropdownSelectProps extends Omit<React.HTMLProps<DropdownSelect
}

/**
* Component for inputting textual values.
*
* This component supports multiline input and adjusts its layout accordingly.
* Component for selecting a single value from a dropdown.
*/
export const DropdownSelect = React.forwardRef<DropdownSelectDerivedElement, DropdownSelectProps>(
(
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
children,
id: idProp,
...etcProps
}: DropdownSelectProps,
forwardedRef,
) => {
const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;
export const DropdownSelect = React.forwardRef<DropdownSelectDerivedElement, DropdownSelectProps>((
{
label,
hint,
indicator,
size = 'medium' as const,
border = false as const,
block = false as const,
variant = 'default' as const,
hiddenLabel = false as const,
className,
children,
id: idProp,
...etcProps
}: DropdownSelectProps,
forwardedRef,
) => {
const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;

return (
<div
return (
<div
className={clsx(
'relative rounded ring-secondary/50 min-w-48',
'focus-within:ring-4',
{
'block': block,
'inline-block align-middle': !block,
},
className,
)}
>
<select
{...etcProps}
ref={forwardedRef}
id={id}
aria-labelledby={labelId}
data-testid="input"
className={clsx(
'relative rounded ring-secondary/50 min-w-48',
'focus-within:ring-4',
'tesseract-design-dropdown-select bg-negative rounded-inherit w-full peer block appearance-none cursor-pointer select-none',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
{
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
},
{
'pt-4': variant === 'alternate',
},
{
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
{
'block': block,
'inline-block align-middle': !block,
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
},
className,
)}
>
<select
{...etcProps}
ref={forwardedRef}
id={id}
aria-labelledby={labelId}
data-testid="input"
{children}
</select>
{
label && (
<label
htmlFor={id}
data-testid="label"
id={labelId}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative select-none',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
<div
data-testid="hint"
className={clsx(
'tesseract-design-dropdown-select bg-negative rounded-inherit w-full peer block appearance-none cursor-pointer select-none',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative select-none',
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
},
{
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
},
{
'pt-4': variant === 'alternate',
},
{
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
{
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
},
)}
>
{children}
</select>
{
label && (
<label
htmlFor={id}
data-testid="label"
id={labelId}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative select-none',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
<div
data-testid="hint"
className={clsx(
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative select-none',
{
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
},
{
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
},
{
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<div
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
>
{hint}
</div>
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
>
{indicator}
{hint}
</div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
},
);
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
>
{indicator}
</div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
});

DropdownSelect.displayName = 'DropdownSelect';
DropdownSelect.displayName = 'DropdownSelect' as const;

DropdownSelect.defaultProps = {
label: undefined,
hint: undefined,
indicator: undefined,
size: 'medium',
border: false,
block: false,
variant: 'default',
hiddenLabel: false,
size: 'medium' as const,
border: false as const,
block: false as const,
variant: 'default' as const,
hiddenLabel: false as const,
};

+ 161
- 159
categories/choice/react/src/components/MenuSelect/index.tsx Переглянути файл

@@ -2,8 +2,14 @@ import * as React from 'react';
import { TextControl } from '@tesseract-design/web-base';
import clsx from 'clsx';

/**
* Derived HTML element of the {@link MenuSelect} component.
*/
export type MenuSelectDerivedElement = HTMLSelectElement;

/**
* Props of the {@link MenuSelect} component.
*/
export interface MenuSelectProps extends Omit<React.HTMLProps<MenuSelectDerivedElement>, 'size' | 'style' | 'label' | 'multiple'> {
/**
* Short textual description indicating the nature of the component's value.
@@ -44,191 +50,187 @@ export interface MenuSelectProps extends Omit<React.HTMLProps<MenuSelectDerivedE
}

/**
* Component for inputting textual values.
*
* This component supports multiline input and adjusts its layout accordingly.
* Component for selecting a single value from a menu.
*/
export const MenuSelect = React.forwardRef<MenuSelectDerivedElement, MenuSelectProps>(
(
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
startingHeight = '15rem',
id: idProp,
...etcProps
}: MenuSelectProps,
forwardedRef,
) => {
const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;
export const MenuSelect = React.forwardRef<MenuSelectDerivedElement, MenuSelectProps>((
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
startingHeight = '15rem',
id: idProp,
...etcProps
}: MenuSelectProps,
forwardedRef,
) => {
const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;

return (
<div
return (
<div
className={clsx(
'relative rounded ring-secondary/50',
'focus-within:ring-4',
{
'block': block,
'inline-block align-middle': !block,
},
className,
)}
>
<select
{...etcProps}
ref={forwardedRef}
id={id}
aria-labelledby={labelId}
data-testid="input"
size={2}
style={{
height: startingHeight,
}}
className={clsx(
'relative rounded ring-secondary/50',
'focus-within:ring-4',
'tesseract-design-menu-select bg-negative rounded-inherit w-full peer block overflow-auto cursor-pointer',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
{
'resize': !block,
'resize-y': block,
},
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
{
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
},
{
'pt-4': variant === 'alternate' && size === 'small',
'pt-5': variant === 'alternate' && size === 'medium',
'pt-8': variant === 'alternate' && size === 'large',
},
{
'py-2.5': variant === 'default' && size === 'small',
'py-3': variant === 'default' && size === 'medium',
'py-5': variant === 'default' && size === 'large',
},
{
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
},
{
'block': block,
'inline-block align-middle': !block,
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
{
'min-h-10': size === 'small',
'min-h-12': size === 'medium',
'min-h-16': size === 'large',
},
className,
)}
>
<select
{...etcProps}
ref={forwardedRef}
id={id}
aria-labelledby={labelId}
data-testid="input"
size={2}
style={{
height: startingHeight,
}}
/>
{
label && (
<label
data-testid="label"
htmlFor={id}
id={labelId}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative select-none',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
<div
data-testid="hint"
className={clsx(
'tesseract-design-menu-select bg-negative rounded-inherit w-full peer block overflow-auto cursor-pointer',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
{
'resize': !block,
'resize-y': block,
},
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative select-none',
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
},
{
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
},
{
'pt-4': variant === 'alternate' && size === 'small',
'pt-5': variant === 'alternate' && size === 'medium',
'pt-8': variant === 'alternate' && size === 'large',
},
{
'py-2.5': variant === 'default' && size === 'small',
'py-3': variant === 'default' && size === 'medium',
'py-5': variant === 'default' && size === 'large',
},
{
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
{
'min-h-10': size === 'small',
'min-h-12': size === 'medium',
'min-h-16': size === 'large',
},
)}
/>
{
label && (
<label
data-testid="label"
htmlFor={id}
id={labelId}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative select-none',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
>
<div
data-testid="hint"
className={clsx(
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative select-none',
{
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
},
{
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
},
{
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<div
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
>
{hint}
</div>
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
>
{indicator}
{hint}
</div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
},
);
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
>
{indicator}
</div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
});

MenuSelect.displayName = 'MenuSelect';
MenuSelect.displayName = 'MenuSelect' as const;

MenuSelect.defaultProps = {
label: undefined,
hint: undefined,
indicator: undefined,
size: 'medium',
border: false,
block: false,
variant: 'default',
hiddenLabel: false,
startingHeight: '15rem',
size: 'medium' as const,
border: false as const,
block: false as const,
variant: 'default' as const,
hiddenLabel: false as const,
startingHeight: '15rem' as const,
};

+ 36
- 7
categories/choice/react/src/components/RadioButton/index.tsx Переглянути файл

@@ -2,22 +2,51 @@ import * as React from 'react';
import clsx from 'clsx';
import { Button } from '@tesseract-design/web-base';

/**
* Derived HTML element of the {@link RadioButton} component.
*/
export type RadioButtonDerivedElement = HTMLInputElement;

/**
* Props of the {@link RadioButton} component.
*/
export interface RadioButtonProps extends Omit<React.InputHTMLAttributes<RadioButtonDerivedElement>, 'type' | 'size'> {
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean;
/**
* Should the component's content use minimal space?
*/
compact?: boolean;
/**
* Size of the component.
*/
size?: Button.Size;
/**
* Complementary content of the component.
*/
subtext?: React.ReactNode;
/**
* Short complementary content displayed at the edge of the component.
*/
badge?: React.ReactNode;
/**
* Variant of the component.
*/
variant?: Button.Variant;
}

/**
* Component for selecting a single value from an array of choices grouped by name.
*
* This component is displayed as a regular button.
*/
export const RadioButton = React.forwardRef<RadioButtonDerivedElement, RadioButtonProps>((
{
children,
block = false,
compact = false,
block = false as const,
compact = false as const,
size = 'medium' as const,
id: idProp,
className,
@@ -141,13 +170,13 @@ export const RadioButton = React.forwardRef<RadioButtonDerivedElement, RadioButt
);
});

RadioButton.displayName = 'RadioButton';
RadioButton.displayName = 'RadioButton' as const;

RadioButton.defaultProps = {
badge: undefined,
block: false,
compact: false,
block: false as const,
compact: false as const,
subtext: undefined,
size: 'medium',
variant: 'filled',
size: 'medium' as const,
variant: 'bare' as const,
};

+ 20
- 3
categories/choice/react/src/components/RadioTickBox/index.tsx Переглянути файл

@@ -1,17 +1,34 @@
import * as React from 'react';
import clsx from 'clsx';

/**
* Derived HTML element of the {@link RadioTickBox} component.
*/
export type RadioTickBoxDerivedElement = HTMLInputElement;

/**
* Props of the {@link RadioTickBox} component.
*/
export interface RadioTickBoxProps extends Omit<React.InputHTMLAttributes<RadioTickBoxDerivedElement>, 'type' | 'size'> {
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean;
/**
* Complementary content of the component.
*/
subtext?: React.ReactNode;
}

/**
* Component for selecting a single value from an array of choices grouped by name.
*
* This component is displayed as a tick box, i.e. a typical radio button.
*/
export const RadioTickBox = React.forwardRef<RadioTickBoxDerivedElement, RadioTickBoxProps>((
{
children,
block = false,
block = false as const,
id: idProp,
className,
subtext,
@@ -84,9 +101,9 @@ export const RadioTickBox = React.forwardRef<RadioTickBoxDerivedElement, RadioTi
);
});

RadioTickBox.displayName = 'RadioTickBox';
RadioTickBox.displayName = 'RadioTickBox' as const;

RadioTickBox.defaultProps = {
block: false,
block: false as const,
subtext: undefined,
};

+ 12
- 0
pnpm-lock.yaml Переглянути файл

@@ -45,6 +45,9 @@ importers:
'@testing-library/react':
specifier: ^13.4.0
version: 13.4.0(react-dom@18.2.0)(react@18.2.0)
'@testing-library/user-event':
specifier: ^14.4.3
version: 14.4.3(@testing-library/dom@8.20.1)
'@types/node':
specifier: ^18.14.1
version: 18.14.1
@@ -1926,6 +1929,15 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: true

/@testing-library/user-event@14.4.3(@testing-library/dom@8.20.1):
resolution: {integrity: sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==}
engines: {node: '>=12', npm: '>=6'}
peerDependencies:
'@testing-library/dom': '>=7.21.4'
dependencies:
'@testing-library/dom': 8.20.1
dev: true

/@theoryofnekomata/formxtra@1.0.3:
resolution: {integrity: sha512-xOzE07Slttpx7vbOWqXfatJ+k44TN4zUjI57A5/sNqUDtHzp3pz94A+AVPGVoBY0QXiwzMjeN4DPMp6U1qlkyg==}
engines: {node: '>=10'}


+ 0
- 45
showcases/web-kitchensink-reactnext/src/pages/categories/code/index.tsx Переглянути файл

@@ -1,45 +0,0 @@
import { NextPage } from 'next';
import { DefaultLayout } from '@/components/DefaultLayout';

const CodePage: NextPage = () => {
return (
<DefaultLayout
title="Code"
>
<main className="mt-8 mb-16 md:mt-16 md:mb-32">
<section>
<div className="container mx-auto px-4">
<h2>
VerifyCodeInput
</h2>
<div>
TODO
</div>
</div>
</section>
<section>
<div className="container mx-auto px-4">
<h2>
CodeBlock
</h2>
<div>
TODO
</div>
</div>
</section>
<section>
<div className="container mx-auto px-4">
<h2>
CodeInput
</h2>
<div>
TODO
</div>
</div>
</section>
</main>
</DefaultLayout>
)
}

export default CodePage;

Завантаження…
Відмінити
Зберегти