Browse Source

Implement temporal components

Implement components with native HTML counterparts except datetime-local
master
TheoryOfNekomata 1 year ago
parent
commit
cc5757b459
26 changed files with 1535 additions and 41 deletions
  1. +3
    -3
      TODO.md
  2. +263
    -0
      categories/formatted/react/src/components/PatternTextInput/TextInput.test.tsx
  3. +232
    -0
      categories/formatted/react/src/components/PatternTextInput/index.tsx
  4. +1
    -0
      categories/formatted/react/src/index.ts
  5. +1
    -1
      categories/freeform/react/src/components/MaskedTextInput/index.tsx
  6. +1
    -1
      categories/freeform/react/src/components/MultilineTextInput/index.tsx
  7. +1
    -1
      categories/freeform/react/src/components/TextInput/index.tsx
  8. +5
    -2
      categories/number/react/package.json
  9. +1
    -1
      categories/number/react/scripts/build.ts
  10. +2
    -2
      categories/number/react/src/components/NumberSpinner/NumberSpinner.css
  11. +242
    -0
      categories/number/react/src/components/NumberSpinner/NumberSpinner.test.tsx
  12. +11
    -7
      categories/number/react/src/components/NumberSpinner/index.tsx
  13. +1
    -1
      categories/number/react/src/index.ts
  14. +11
    -0
      categories/number/react/src/web-number-react.test.ts
  15. +5
    -16
      categories/temporal/react/src/components/DateDropdown/index.tsx
  16. +2
    -0
      docs/00-philosophy.md
  17. +11
    -2
      pnpm-lock.yaml
  18. +237
    -0
      showcases/web-kitchensink-reactnext/src/components/temporal/TimeSpinner/index.tsx
  19. +215
    -0
      showcases/web-kitchensink-reactnext/src/components/temporal/YearMonthInput/index.tsx
  20. +215
    -0
      showcases/web-kitchensink-reactnext/src/components/temporal/YearWeekInput/index.tsx
  21. +3
    -0
      showcases/web-kitchensink-reactnext/src/components/temporal/index.ts
  22. +1
    -1
      showcases/web-kitchensink-reactnext/src/pages/_app.tsx
  23. +1
    -0
      showcases/web-kitchensink-reactnext/src/pages/categories/formatted/index.tsx
  24. +23
    -2
      showcases/web-kitchensink-reactnext/src/pages/categories/number/index.tsx
  25. +46
    -0
      showcases/web-kitchensink-reactnext/src/pages/categories/temporal/index.tsx
  26. +1
    -1
      showcases/web-kitchensink-reactnext/tailwind.config.js

+ 3
- 3
TODO.md View File

@@ -59,9 +59,9 @@
- [ ] DurationInput - [ ] DurationInput
- [ ] MonthInput - [ ] MonthInput
- [ ] MonthDayInput - [ ] MonthDayInput
- [ ] TimeSpinner
- [ ] YearMonthInput
- [ ] YearWeekInput
- [X] TimeSpinner
- [-] YearMonthInput
- [-] YearWeekInput
- [ ] YearInput - [ ] YearInput


# Others # Others


+ 263
- 0
categories/formatted/react/src/components/PatternTextInput/TextInput.test.tsx View File

@@ -0,0 +1,263 @@
import * as React from 'react';
import {
cleanup,
render,
screen,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TextControl } from '@tesseract-design/web-base';
import {
vi,
expect,
describe,
it,
afterEach,
} from 'vitest';
import matchers from '@testing-library/jest-dom/matchers';
import {
TextInput,
TextInputDerivedElement,
} from '.';

expect.extend(matchers);

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

it('renders a textbox', () => {
render(
<TextInput />,
);
const textbox = screen.getByRole('textbox');
expect(textbox).toBeInTheDocument();
expect(textbox).toHaveProperty('type', 'text');
});

it('renders a border', () => {
render(
<TextInput
border
/>,
);
const border = screen.getByTestId('border');
expect(border).toBeInTheDocument();
});

it('renders a label', () => {
render(
<TextInput
label="foo"
/>,
);
const textbox = screen.getByLabelText('foo');
expect(textbox).toBeInTheDocument();
const label = screen.getByTestId('label');
expect(label).toHaveTextContent('foo');
});

it('renders a hidden label', () => {
render(
<TextInput
label="foo"
hiddenLabel
/>,
);
const textbox = screen.getByLabelText('foo');
expect(textbox).toBeInTheDocument();
const label = screen.queryByTestId('label');
expect(label).toBeInTheDocument();
expect(label).toHaveClass('sr-only');
});

it('renders a hint', () => {
render(
<TextInput
hint="foo"
/>,
);
const hint = screen.getByTestId('hint');
expect(hint).toBeInTheDocument();
});

it('renders an indicator', () => {
render(
<TextInput
indicator={
<div />
}
/>,
);
const indicator = screen.getByTestId('indicator');
expect(indicator).toBeInTheDocument();
});

describe.each`
size | inputClassName | hintClassName | indicatorClassName
${'small'} | ${'h-10'} | ${'pr-10'} | ${'w-10'}
${'medium'} | ${'h-12'} | ${'pr-12'} | ${'w-12'}
${'large'} | ${'h-16'} | ${'pr-16'} | ${'w-16'}
`('on $size size', ({
size,
inputClassName,
hintClassName,
indicatorClassName,
}: {
size: TextControl.Size,
inputClassName: string,
hintClassName: string,
indicatorClassName: string,
}) => {
it('renders input styles', () => {
render(
<TextInput
size={size}
/>,
);

const input = screen.getByTestId('input');
expect(input).toHaveClass(inputClassName);
});

it('renders label styles with indicator', () => {
render(
<TextInput
size={size}
label="foo"
indicator={<div />}
/>,
);
const label = screen.getByTestId('label');
expect(label).toHaveClass(hintClassName);
});

it('renders hint styles when indicator is present', () => {
render(
<TextInput
size={size}
hint="hint"
indicator={<div />}
/>,
);

const hint = screen.getByTestId('hint');
expect(hint).toHaveClass(hintClassName);
});

it('renders indicator styles', () => {
render(
<TextInput
size={size}
indicator={
<div />
}
/>,
);

const indicator = screen.getByTestId('indicator');
expect(indicator).toHaveClass(indicatorClassName);
});
});

it('renders a block textbox', () => {
render(
<TextInput
block
/>,
);

const base = screen.getByTestId('base');
expect(base).toHaveClass('block');
});

it.each(TextControl.AVAILABLE_INPUT_TYPES)('renders a textbox with type %s', (inputType) => {
render(
<TextInput
type={inputType}
/>,
);
const textbox = screen.getByTestId('input');
expect(textbox).toHaveProperty('type', inputType);
});

it('falls back to text input mode when it clashes with the input type', () => {
render(
<TextInput
type="text"
inputMode="search"
/>,
);
const textbox = screen.getByTestId('input');
expect(textbox).toHaveProperty('inputMode', 'text');
});

describe.each`
variant | inputClassName | hintClassName
${'default'} | ${'pl-4'} | ${'bottom-0 pl-4 pb-1'}
${'alternate'} | ${'pl-1.5 pt-4'} | ${'top-0.5'}
`('on $variant style', ({
variant,
inputClassName,
hintClassName,
}: {
variant: TextControl.Variant,
inputClassName: string,
hintClassName: string,
}) => {
it('renders input styles', () => {
render(
<TextInput
variant={variant}
/>,
);

const input = screen.getByTestId('input');
expect(input).toHaveClass(inputClassName);
});

it('renders hint styles', () => {
render(
<TextInput
variant={variant}
hint="hint"
/>,
);

const hint = screen.getByTestId('hint');
expect(hint).toHaveClass(hintClassName);
});
});

it('handles change events', async () => {
const onChange = vi.fn().mockImplementationOnce(
(e: React.ChangeEvent<TextInputDerivedElement>) => {
e.preventDefault();
},
);
render(
<TextInput
onChange={onChange}
/>,
);
const textbox: HTMLInputElement = screen.getByRole('textbox');
await userEvent.type(textbox, 'foobar');
expect(onChange).toBeCalled();
});

it('handles input events', async () => {
const onInput = vi.fn().mockImplementationOnce(
(e: React.SyntheticEvent<TextInputDerivedElement>) => {
e.preventDefault();
},
);
render(
<TextInput
onInput={onInput}
/>,
);
const textbox: HTMLInputElement = screen.getByTestId('input');
await userEvent.type(textbox, 'foobar');
expect(onInput).toBeCalled();
});
});

+ 232
- 0
categories/formatted/react/src/components/PatternTextInput/index.tsx View File

@@ -0,0 +1,232 @@
import * as React from 'react';
import { TextControl } from '@tesseract-design/web-base';
import clsx from 'clsx';

export type PatternTextInputDerivedElement = HTMLInputElement;

export interface PatternTextInputProps extends Omit<React.HTMLProps<PatternTextInputDerivedElement>, 'size' | 'type' | 'label' | 'list' | 'inputMode'> {
/**
* Short textual description indicating the nature of the component's value.
*/
label?: React.ReactNode,
/**
* Short textual description as guidelines for valid input values.
*/
hint?: React.ReactNode,
/**
* Size of the component.
*/
size?: TextControl.Size,
/**
* Additional description, usually graphical, indicating the nature of the component's value.
*/
indicator?: React.ReactNode,
/**
* Should the component display a border?
*/
border?: boolean,
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean,
/**
* Type of the component value.
*/
type?: TextControl.InputType,
/**
* Style of the component.
*/
variant?: TextControl.Variant,
/**
* Is the label hidden?
*/
hiddenLabel?: boolean,
/**
* Input mode of the component.
*/
inputMode?: TextControl.InputMode,
}

/**
* Component for inputting textual values.
*
* This component supports multiline input and adjusts its layout accordingly.
*/
export const PatternTextInput = React.forwardRef<PatternTextInputDerivedElement, PatternTextInputProps>((
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
type = 'text' as const,
variant = 'default' as const,
hiddenLabel = false,
className,
id: idProp,
style,
inputMode = type,
...etcProps
},
forwardedRef,
) => {
const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;

let resultInputMode = inputMode as React.HTMLProps<PatternTextInputDerivedElement>['inputMode'];
if (type === 'text' && resultInputMode === 'search') {
resultInputMode = 'text';
} else if (type === 'search' && resultInputMode === 'text') {
resultInputMode = 'search';
}

return (
<div
className={clsx(
'relative rounded ring-secondary/50 overflow-hidden',
'focus-within:ring-4',
{
'block': block,
'inline-block align-middle': !block,
},
className,
)}
style={style}
data-testid="base"
>
<input
{...etcProps}
ref={forwardedRef}
aria-labelledby={labelId}
id={id}
type={type}
inputMode={resultInputMode}
data-testid="input"
className={clsx(
'bg-negative rounded-inherit w-full peer block font-inherit',
'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',
},
{
'h-10': size === 'small',
'h-12': size === 'medium',
'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
data-testid="indicator"
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>
);
});

PatternTextInput.displayName = 'PatternTextInput';

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

+ 1
- 0
categories/formatted/react/src/index.ts View File

@@ -1,3 +1,4 @@
export * from './components/EmailInput'; export * from './components/EmailInput';
export * from './components/PatternTextInput';
export * from './components/PhoneNumberInput'; export * from './components/PhoneNumberInput';
export * from './components/UrlInput'; export * from './components/UrlInput';

+ 1
- 1
categories/freeform/react/src/components/MaskedTextInput/index.tsx View File

@@ -4,7 +4,7 @@ import clsx from 'clsx';


export type MaskedTextInputDerivedElement = HTMLInputElement; export type MaskedTextInputDerivedElement = HTMLInputElement;


export interface MaskedTextInputProps extends Omit<React.HTMLProps<MaskedTextInputDerivedElement>, 'size' | 'type' | 'label'> {
export interface MaskedTextInputProps extends Omit<React.HTMLProps<MaskedTextInputDerivedElement>, 'size' | 'type' | 'label' | 'pattern'> {
/** /**
* Short textual description indicating the nature of the component's value. * Short textual description indicating the nature of the component's value.
*/ */


+ 1
- 1
categories/freeform/react/src/components/MultilineTextInput/index.tsx View File

@@ -4,7 +4,7 @@ import clsx from 'clsx';


export type MultilineTextInputDerivedElement = HTMLTextAreaElement; export type MultilineTextInputDerivedElement = HTMLTextAreaElement;


export interface MultilineTextInputProps extends Omit<React.HTMLProps<MultilineTextInputDerivedElement>, 'size' | 'label' | 'inputMode'> {
export interface MultilineTextInputProps extends Omit<React.HTMLProps<MultilineTextInputDerivedElement>, 'size' | 'label' | 'inputMode' | 'pattern'> {
/** /**
* Short textual description indicating the nature of the component's value. * Short textual description indicating the nature of the component's value.
*/ */


+ 1
- 1
categories/freeform/react/src/components/TextInput/index.tsx View File

@@ -4,7 +4,7 @@ import clsx from 'clsx';


export type TextInputDerivedElement = HTMLInputElement; export type TextInputDerivedElement = HTMLInputElement;


export interface TextInputProps extends Omit<React.HTMLProps<TextInputDerivedElement>, 'size' | 'type' | 'label' | 'list' | 'inputMode'> {
export interface TextInputProps extends Omit<React.HTMLProps<TextInputDerivedElement>, 'size' | 'type' | 'label' | 'list' | 'inputMode' | 'pattern'> {
/** /**
* Short textual description indicating the nature of the component's value. * Short textual description indicating the nature of the component's value.
*/ */


+ 5
- 2
categories/number/react/package.json View File

@@ -15,8 +15,11 @@
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"@types/node": "^18.14.1", "@types/node": "^18.14.1",
"@types/react": "^18.0.27", "@types/react": "^18.0.27",
"@types/testing-library__jest-dom": "^5.14.7",
"@vitest/coverage-v8": "^0.33.0",
"eslint": "^8.35.0", "eslint": "^8.35.0",
"eslint-config-lxsmnsyc": "^0.5.0", "eslint-config-lxsmnsyc": "^0.5.0",
"jsdom": "^21.1.0", "jsdom": "^21.1.0",
@@ -27,7 +30,7 @@
"tslib": "^2.5.0", "tslib": "^2.5.0",
"tsx": "^3.12.7", "tsx": "^3.12.7",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"vitest": "^0.28.1"
"vitest": "^0.33.0"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0", "react": "^16.8 || ^17.0 || ^18.0",
@@ -76,7 +79,7 @@
"types": "./dist/types/index.d.ts" "types": "./dist/types/index.d.ts"
}, },
"./dist/Slider.css": "./dist/Slider.css", "./dist/Slider.css": "./dist/Slider.css",
"./dist/Spinner.css": "./dist/Spinner.css"
"./dist/NumberSpinner.css": "./dist/NumberSpinner.css"
}, },
"typesVersions": { "typesVersions": {
"*": {} "*": {}


+ 1
- 1
categories/number/react/scripts/build.ts View File

@@ -17,4 +17,4 @@ const doCopy = (src: string, dest: string) => {
} }


doCopy('./src/components/Slider/Slider.css', './dist/Slider.css'); doCopy('./src/components/Slider/Slider.css', './dist/Slider.css');
doCopy('./src/components/Spinner/Spinner.css', './dist/Spinner.css');
doCopy('./src/components/NumberSpinner/NumberSpinner.css', './dist/NumberSpinner.css');

categories/number/react/src/components/Spinner/Spinner.css → categories/number/react/src/components/NumberSpinner/NumberSpinner.css View File

@@ -1,8 +1,8 @@
.tesseract-design-spinner {
.tesseract-design-number-spinner {
position: relative; position: relative;
} }


.tesseract-design-spinner::-webkit-inner-spin-button {
.tesseract-design-number-spinner::-webkit-inner-spin-button {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;

+ 242
- 0
categories/number/react/src/components/NumberSpinner/NumberSpinner.test.tsx View File

@@ -0,0 +1,242 @@
import * as React from 'react';
import {
cleanup,
render,
screen,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TextControl } from '@tesseract-design/web-base';
import {
vi,
expect,
describe,
it,
afterEach,
} from 'vitest';
import matchers from '@testing-library/jest-dom/matchers';
import {
NumberSpinner,
NumberSpinnerDerivedElement,
} from '.';

expect.extend(matchers);

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

it('renders a number input', () => {
render(
<NumberSpinner />,
);
const textbox = screen.getByTestId('input');
expect(textbox).toBeInTheDocument();
expect(textbox).toHaveProperty('type', 'number');
});

it('renders a border', () => {
render(
<NumberSpinner
border
/>,
);
const border = screen.getByTestId('border');
expect(border).toBeInTheDocument();
});

it('renders a label', () => {
render(
<NumberSpinner
label="foo"
/>,
);
const textbox = screen.getByLabelText('foo');
expect(textbox).toBeInTheDocument();
const label = screen.getByTestId('label');
expect(label).toHaveTextContent('foo');
});

it('renders a hidden label', () => {
render(
<NumberSpinner
label="foo"
hiddenLabel
/>,
);
const textbox = screen.getByLabelText('foo');
expect(textbox).toBeInTheDocument();
const label = screen.queryByTestId('label');
expect(label).toBeInTheDocument();
expect(label).toHaveClass('sr-only');
});

it('renders a hint', () => {
render(
<NumberSpinner
hint="foo"
/>,
);
const hint = screen.getByTestId('hint');
expect(hint).toBeInTheDocument();
});

it('renders an indicator', () => {
render(
<NumberSpinner
indicator={
<div />
}
/>,
);
const indicator = screen.getByTestId('indicator');
expect(indicator).toBeInTheDocument();
});

describe.each`
size | inputClassName | hintClassName | indicatorClassName
${'small'} | ${'h-10'} | ${'pr-10'} | ${'w-10'}
${'medium'} | ${'h-12'} | ${'pr-12'} | ${'w-12'}
${'large'} | ${'h-16'} | ${'pr-16'} | ${'w-16'}
`('on $size size', ({
size,
inputClassName,
hintClassName,
indicatorClassName,
}: {
size: TextControl.Size,
inputClassName: string,
hintClassName: string,
indicatorClassName: string,
}) => {
it('renders input styles', () => {
render(
<NumberSpinner
size={size}
/>,
);

const input = screen.getByTestId('input');
expect(input).toHaveClass(inputClassName);
});

it('renders label styles with indicator', () => {
render(
<NumberSpinner
size={size}
label="foo"
indicator={<div />}
/>,
);
const label = screen.getByTestId('label');
expect(label).toHaveClass(hintClassName);
});

it('renders hint styles when indicator is present', () => {
render(
<NumberSpinner
size={size}
hint="hint"
indicator={<div />}
/>,
);

const hint = screen.getByTestId('hint');
expect(hint).toHaveClass(hintClassName);
});

it('renders indicator styles', () => {
render(
<NumberSpinner
size={size}
indicator={
<div />
}
/>,
);

const indicator = screen.getByTestId('indicator');
expect(indicator).toHaveClass(indicatorClassName);
});
});

it('renders a block textbox', () => {
render(
<NumberSpinner
block
/>,
);

const base = screen.getByTestId('base');
expect(base).toHaveClass('block');
});

describe.each`
variant | inputClassName | hintClassName
${'default'} | ${'pl-4'} | ${'bottom-0 pl-4 pb-1'}
${'alternate'} | ${'pl-1.5 pt-4'} | ${'top-0.5'}
`('on $variant style', ({
variant,
inputClassName,
hintClassName,
}: {
variant: TextControl.Variant,
inputClassName: string,
hintClassName: string,
}) => {
it('renders input styles', () => {
render(
<NumberSpinner
variant={variant}
/>,
);

const input = screen.getByTestId('input');
expect(input).toHaveClass(inputClassName);
});

it('renders hint styles', () => {
render(
<NumberSpinner
variant={variant}
hint="hint"
/>,
);

const hint = screen.getByTestId('hint');
expect(hint).toHaveClass(hintClassName);
});
});

it('handles change events', async () => {
const onChange = vi.fn().mockImplementationOnce(
(e: React.ChangeEvent<NumberSpinnerDerivedElement>) => {
e.preventDefault();
},
);
render(
<NumberSpinner
onChange={onChange}
/>,
);
const textbox = screen.getByTestId('input');
await userEvent.type(textbox, '69420');
expect(onChange).toBeCalled();
});

it('handles input events', async () => {
const onInput = vi.fn().mockImplementationOnce(
(e: React.SyntheticEvent<NumberSpinnerDerivedElement>) => {
e.preventDefault();
},
);
render(
<NumberSpinner
onInput={onInput}
/>,
);
const textbox = screen.getByTestId('input');
await userEvent.type(textbox, '69420');
expect(onInput).toBeCalled();
});
});

categories/number/react/src/components/Spinner/index.tsx → categories/number/react/src/components/NumberSpinner/index.tsx View File

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


export type SpinnerDerivedElement = HTMLInputElement;
export type NumberSpinnerDerivedElement = HTMLInputElement;


export interface SpinnerProps extends Omit<React.HTMLProps<SpinnerDerivedElement>, 'size' | 'type' | 'label'> {
export interface NumberSpinnerProps extends Omit<React.HTMLProps<NumberSpinnerDerivedElement>, 'size' | 'type' | 'label'> {
/** /**
* Short textual description indicating the nature of the component's value. * Short textual description indicating the nature of the component's value.
*/ */
@@ -42,7 +42,7 @@ export interface SpinnerProps extends Omit<React.HTMLProps<SpinnerDerivedElement
/** /**
* Component for inputting numeric values. * Component for inputting numeric values.
*/ */
export const Spinner = React.forwardRef<SpinnerDerivedElement, SpinnerProps>((
export const NumberSpinner = React.forwardRef<NumberSpinnerDerivedElement, NumberSpinnerProps>((
{ {
label, label,
hint, hint,
@@ -54,8 +54,9 @@ export const Spinner = React.forwardRef<SpinnerDerivedElement, SpinnerProps>((
hiddenLabel = false, hiddenLabel = false,
className, className,
id: idProp, id: idProp,
style,
...etcProps ...etcProps
}: SpinnerProps,
}: NumberSpinnerProps,
forwardedRef, forwardedRef,
) => { ) => {
const labelId = React.useId(); const labelId = React.useId();
@@ -73,6 +74,8 @@ export const Spinner = React.forwardRef<SpinnerDerivedElement, SpinnerProps>((
}, },
className, className,
)} )}
style={style}
data-testid="base"
> >
<input <input
{...etcProps} {...etcProps}
@@ -85,7 +88,7 @@ export const Spinner = React.forwardRef<SpinnerDerivedElement, SpinnerProps>((
'bg-negative rounded-inherit w-full peer block tabular-nums font-inherit', 'bg-negative rounded-inherit w-full peer block tabular-nums font-inherit',
'focus:outline-0', 'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed', 'disabled:opacity-50 disabled:cursor-not-allowed',
'tesseract-design-spinner',
'tesseract-design-number-spinner',
{ {
'text-xxs': size === 'small', 'text-xxs': size === 'small',
'text-xs': size === 'medium', 'text-xs': size === 'medium',
@@ -171,6 +174,7 @@ export const Spinner = React.forwardRef<SpinnerDerivedElement, SpinnerProps>((
)} )}
{indicator && ( {indicator && (
<div <div
data-testid="indicator"
className={clsx( 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', 'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none',
{ {
@@ -193,9 +197,9 @@ export const Spinner = React.forwardRef<SpinnerDerivedElement, SpinnerProps>((
); );
}); });


Spinner.displayName = 'Spinner';
NumberSpinner.displayName = 'NumberSpinner';


Spinner.defaultProps = {
NumberSpinner.defaultProps = {
label: undefined, label: undefined,
hint: undefined, hint: undefined,
indicator: undefined, indicator: undefined,

+ 1
- 1
categories/number/react/src/index.ts View File

@@ -1,2 +1,2 @@
export * from './components/Slider'; export * from './components/Slider';
export * from './components/Spinner';
export * from './components/NumberSpinner';

+ 11
- 0
categories/number/react/src/web-number-react.test.ts View File

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

describe('web-number-react', () => {
it.each([
'NumberSpinner',
'Slider',
])('exports %s', (namedExport) => {
expect(WebNumberReact).toHaveProperty(namedExport);
});
});

+ 5
- 16
categories/temporal/react/src/components/DateDropdown/index.tsx View File

@@ -4,7 +4,7 @@ import clsx from 'clsx';


export type DateDropdownDerivedElement = HTMLInputElement; export type DateDropdownDerivedElement = HTMLInputElement;


export interface DateDropdownProps extends Omit<React.HTMLProps<DateDropdownDerivedElement>, 'size' | 'type' | 'label'> {
export interface DateDropdownProps extends Omit<React.HTMLProps<DateDropdownDerivedElement>, 'size' | 'type' | 'label' | 'pattern'> {
/** /**
* Short textual description indicating the nature of the component's value. * Short textual description indicating the nature of the component's value.
*/ */
@@ -58,8 +58,6 @@ export const DateDropdown = React.forwardRef<
className, className,
id: idProp, id: idProp,
style, style,
onFocus,
onClick,
...etcProps ...etcProps
}: DateDropdownProps, }: DateDropdownProps,
forwardedRef, forwardedRef,
@@ -68,16 +66,6 @@ export const DateDropdown = React.forwardRef<
const defaultId = React.useId(); const defaultId = React.useId();
const id = idProp ?? defaultId; const id = idProp ?? defaultId;


const handleFocus: React.FocusEventHandler<DateDropdownDerivedElement> = (e) => {
e.currentTarget.showPicker();
onFocus?.(e);
};

const handleClick: React.MouseEventHandler<DateDropdownDerivedElement> = (e) => {
e.currentTarget.showPicker();
onClick?.(e);
};

return ( return (
<div <div
className={clsx( className={clsx(
@@ -90,6 +78,7 @@ export const DateDropdown = React.forwardRef<
className, className,
)} )}
style={style} style={style}
data-testid="base"
> >
<input <input
{...etcProps} {...etcProps}
@@ -98,10 +87,9 @@ export const DateDropdown = React.forwardRef<
aria-labelledby={labelId} aria-labelledby={labelId}
type="date" type="date"
data-testid="input" data-testid="input"
onFocus={handleFocus}
onClick={handleClick}
pattern="\d{4}-\d{2}-\d{2}"
className={clsx( className={clsx(
'bg-negative rounded-inherit w-full peer block font-inherit',
'bg-negative rounded-inherit w-full peer block font-inherit tabular-nums',
'focus:outline-0', 'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed', 'disabled:opacity-50 disabled:cursor-not-allowed',
{ {
@@ -189,6 +177,7 @@ export const DateDropdown = React.forwardRef<
)} )}
{indicator && ( {indicator && (
<div <div
data-testid="indicator"
className={clsx( 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', 'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none',
{ {


+ 2
- 0
docs/00-philosophy.md View File

@@ -34,3 +34,5 @@
elements may have different markup and appearance. Tesseract elements may have different markup and appearance. Tesseract
ensures components are still usable on most assistive technologies given ensures components are still usable on most assistive technologies given
both versions of the user interface. both versions of the user interface.
- All components are named in order to depict the purpose, the nature of data
being operated, and UX expectations.

+ 11
- 2
pnpm-lock.yaml View File

@@ -579,12 +579,21 @@ importers:
'@testing-library/react': '@testing-library/react':
specifier: ^13.4.0 specifier: ^13.4.0
version: 13.4.0(react-dom@18.2.0)(react@18.2.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': '@types/node':
specifier: ^18.14.1 specifier: ^18.14.1
version: 18.14.1 version: 18.14.1
'@types/react': '@types/react':
specifier: ^18.0.27 specifier: ^18.0.27
version: 18.2.14 version: 18.2.14
'@types/testing-library__jest-dom':
specifier: ^5.14.7
version: 5.14.7
'@vitest/coverage-v8':
specifier: ^0.33.0
version: 0.33.0(vitest@0.33.0)
eslint: eslint:
specifier: ^8.35.0 specifier: ^8.35.0
version: 8.43.0 version: 8.43.0
@@ -616,8 +625,8 @@ importers:
specifier: ^4.9.5 specifier: ^4.9.5
version: 4.9.5 version: 4.9.5
vitest: vitest:
specifier: ^0.28.1
version: 0.28.1(jsdom@21.1.0)
specifier: ^0.33.0
version: 0.33.0(jsdom@21.1.0)


categories/temporal/react: categories/temporal/react:
dependencies: dependencies:


+ 237
- 0
showcases/web-kitchensink-reactnext/src/components/temporal/TimeSpinner/index.tsx View File

@@ -0,0 +1,237 @@
import * as React from 'react';
import { TextControl } from '@tesseract-design/web-base';
import clsx from 'clsx';

export type TimeSpinnerDerivedElement = HTMLInputElement;

type Digit = (0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9);

type Segment = `${Digit}${Digit}`;

type StepHhMm = `${Segment}:${Segment}`;

type StepHhMmSs = `${StepHhMm}:${Segment}`;

type Step = StepHhMm | StepHhMmSs;

export interface TimeSpinnerProps extends Omit<React.HTMLProps<TimeSpinnerDerivedElement>, 'size' | 'type' | 'label' | 'step' | 'pattern'> {
/**
* Short textual description indicating the nature of the component's value.
*/
label?: React.ReactNode,
/**
* Short textual description as guidelines for valid input values.
*/
hint?: React.ReactNode,
/**
* Size of the component.
*/
size?: TextControl.Size,
/**
* Additional description, usually graphical, indicating the nature of the component's value.
*/
indicator?: React.ReactNode,
/**
* Should the component display a border?
*/
border?: boolean,
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean,
/**
* Style of the component.
*/
variant?: TextControl.Variant,
/**
* Is the label hidden?
*/
hiddenLabel?: boolean,
/**
* Should the component display seconds?
*/
displaySeconds?: boolean,
/**
* Step size for the component.
*/
step?: Step,
}

/**
* Component for inputting textual values.
*/
export const TimeSpinner = React.forwardRef<
TimeSpinnerDerivedElement,
TimeSpinnerProps
>((
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
id: idProp,
style,
displaySeconds = false,
step = '00:01',
...etcProps
}: TimeSpinnerProps,
forwardedRef,
) => {
const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;

const [hh, mm, ss = 0] = step.split(':').map((s: string) => parseInt(s, 10));
const stepValue = ss + (mm * 60) + (hh * 3600);

return (
<div
className={clsx(
'relative rounded ring-secondary/50 overflow-hidden',
'focus-within:ring-4',
{
'block': block,
'inline-block align-middle': !block,
},
className,
)}
style={style}
data-testid="base"
>
<input
{...etcProps}
ref={forwardedRef}
id={id}
aria-labelledby={labelId}
type="time"
data-testid="input"
step={displaySeconds && stepValue > 60 ? 1 : stepValue}
pattern="\d{2}:\d{2}(:\d{2})?"
className={clsx(
'bg-negative rounded-inherit w-full peer block font-inherit tabular-nums',
'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',
},
{
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
},
)}
/>
{label && (
<label
data-testid="label"
id={labelId}
htmlFor={id}
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',
},
)}
>
{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>
);
});

TimeSpinner.displayName = 'TimeSpinner';

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

+ 215
- 0
showcases/web-kitchensink-reactnext/src/components/temporal/YearMonthInput/index.tsx View File

@@ -0,0 +1,215 @@
import * as React from 'react';
import { TextControl } from '@tesseract-design/web-base';
import clsx from 'clsx';

export type YearMonthInputDerivedElement = HTMLInputElement;

export interface YearMonthInputProps extends Omit<React.HTMLProps<YearMonthInputDerivedElement>, 'size' | 'type' | 'label' | 'pattern'> {
/**
* Short textual description indicating the nature of the component's value.
*/
label?: React.ReactNode,
/**
* Short textual description as guidelines for valid input values.
*/
hint?: React.ReactNode,
/**
* Size of the component.
*/
size?: TextControl.Size,
/**
* Additional description, usually graphical, indicating the nature of the component's value.
*/
indicator?: React.ReactNode,
/**
* Should the component display a border?
*/
border?: boolean,
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean,
/**
* Style of the component.
*/
variant?: TextControl.Variant,
/**
* Is the label hidden?
*/
hiddenLabel?: boolean,
}

/**
* Component for inputting textual values.
*/
export const YearMonthInput = React.forwardRef<
YearMonthInputDerivedElement,
YearMonthInputProps
>((
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
id: idProp,
style,
...etcProps
}: YearMonthInputProps,
forwardedRef,
) => {
const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;

// TODO render enhanced version

return (
<div
className={clsx(
'relative rounded ring-secondary/50 overflow-hidden',
'focus-within:ring-4',
{
'block': block,
'inline-block align-middle': !block,
},
className,
)}
style={style}
data-testid="base"
>
<input
{...etcProps}
ref={forwardedRef}
id={id}
aria-labelledby={labelId}
type="month"
data-testid="input"
pattern="\d{4}-\d{2}"
className={clsx(
'bg-negative rounded-inherit w-full peer block font-inherit tabular-nums',
'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',
},
{
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
},
)}
/>
{label && (
<label
data-testid="label"
id={labelId}
htmlFor={id}
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',
},
)}
>
{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>
);
});

YearMonthInput.displayName = 'YearMonthInput';

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

+ 215
- 0
showcases/web-kitchensink-reactnext/src/components/temporal/YearWeekInput/index.tsx View File

@@ -0,0 +1,215 @@
import * as React from 'react';
import { TextControl } from '@tesseract-design/web-base';
import clsx from 'clsx';

export type YearWeekInputDerivedElement = HTMLInputElement;

export interface YearWeekInputProps extends Omit<React.HTMLProps<YearWeekInputDerivedElement>, 'size' | 'type' | 'label' | 'pattern'> {
/**
* Short textual description indicating the nature of the component's value.
*/
label?: React.ReactNode,
/**
* Short textual description as guidelines for valid input values.
*/
hint?: React.ReactNode,
/**
* Size of the component.
*/
size?: TextControl.Size,
/**
* Additional description, usually graphical, indicating the nature of the component's value.
*/
indicator?: React.ReactNode,
/**
* Should the component display a border?
*/
border?: boolean,
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean,
/**
* Style of the component.
*/
variant?: TextControl.Variant,
/**
* Is the label hidden?
*/
hiddenLabel?: boolean,
}

/**
* Component for inputting textual values.
*/
export const YearWeekInput = React.forwardRef<
YearWeekInputDerivedElement,
YearWeekInputProps
>((
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
id: idProp,
style,
...etcProps
}: YearWeekInputProps,
forwardedRef,
) => {
const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;

// TODO render enhanced version

return (
<div
className={clsx(
'relative rounded ring-secondary/50 overflow-hidden',
'focus-within:ring-4',
{
'block': block,
'inline-block align-middle': !block,
},
className,
)}
style={style}
data-testid="base"
>
<input
{...etcProps}
ref={forwardedRef}
id={id}
aria-labelledby={labelId}
type="week"
data-testid="input"
pattern="\d{4}-W\d{,2}"
className={clsx(
'bg-negative rounded-inherit w-full peer block font-inherit tabular-nums',
'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',
},
{
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
},
)}
/>
{label && (
<label
data-testid="label"
id={labelId}
htmlFor={id}
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',
},
)}
>
{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>
);
});

YearWeekInput.displayName = 'YearWeekInput';

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

+ 3
- 0
showcases/web-kitchensink-reactnext/src/components/temporal/index.ts View File

@@ -0,0 +1,3 @@
export * from './TimeSpinner';
export * from './YearMonthInput';
export * from './YearWeekInput';

+ 1
- 1
showcases/web-kitchensink-reactnext/src/pages/_app.tsx View File

@@ -12,8 +12,8 @@ import '@tesseract-design/web-multichoice-react/dist/ToggleButton.css'
import '@tesseract-design/web-multichoice-react/dist/ToggleSwitch.css' import '@tesseract-design/web-multichoice-react/dist/ToggleSwitch.css'
import '@tesseract-design/web-multichoice-react/dist/ToggleTickBox.css' import '@tesseract-design/web-multichoice-react/dist/ToggleTickBox.css'


import '@tesseract-design/web-number-react/dist/NumberSpinner.css'
import '@tesseract-design/web-number-react/dist/Slider.css' import '@tesseract-design/web-number-react/dist/Slider.css'
import '@tesseract-design/web-number-react/dist/Spinner.css'


import '@modal-sh/react-refractor/dist/Refractor.css' import '@modal-sh/react-refractor/dist/Refractor.css'




+ 1
- 0
showcases/web-kitchensink-reactnext/src/pages/categories/formatted/index.tsx View File

@@ -12,6 +12,7 @@ const TemporalPage: NextPage = () => {
label="Phone" label="Phone"
name="phone" name="phone"
enhanced enhanced
border
onFocus={(e) => { console.log('focus', e.currentTarget)}} onFocus={(e) => { console.log('focus', e.currentTarget)}}
onBlur={(e) => { console.log('blur', e.currentTarget)}} onBlur={(e) => { console.log('blur', e.currentTarget)}}
onChange={(e) => { console.log('change', e.currentTarget.name, e.currentTarget, e.currentTarget.value)}} onChange={(e) => { console.log('change', e.currentTarget.name, e.currentTarget, e.currentTarget.value)}}


+ 23
- 2
showcases/web-kitchensink-reactnext/src/pages/categories/number/index.tsx View File

@@ -6,9 +6,9 @@ import {Section, Subsection} from '@/components/Section';
const NumberPage: NextPage = () => { const NumberPage: NextPage = () => {
return ( return (
<main className="my-16 md:my-32"> <main className="my-16 md:my-32">
<Section title="Spinner">
<Section title="NumberSpinner">
<Subsection title="Default"> <Subsection title="Default">
<TesseractNumber.Spinner
<TesseractNumber.NumberSpinner
min={-100} min={-100}
max={100} max={100}
step="any" step="any"
@@ -46,6 +46,27 @@ const NumberPage: NextPage = () => {
</TesseractNumber.Slider> </TesseractNumber.Slider>
</Subsection> </Subsection>
<Subsection title="Vertical"> <Subsection title="Vertical">
<TesseractNumber.Slider
min={-100}
max={100}
orient="vertical"
>
<optgroup label="Test Values">
<option value={-100}>
Lowest
</option>
<option value={25}>
日本語
</option>
<option value={50} />
<option value={100}>
Highest
</option>
<option value={200}>
Out of bounds
</option>
</optgroup>
</TesseractNumber.Slider>
<TesseractNumber.Slider <TesseractNumber.Slider
min={-100} min={-100}
max={100} max={100}


+ 46
- 0
showcases/web-kitchensink-reactnext/src/pages/categories/temporal/index.tsx View File

@@ -2,6 +2,7 @@ import {NextPage} from 'next';
import {DefaultLayout} from '@/components/DefaultLayout'; import {DefaultLayout} from '@/components/DefaultLayout';
import {Section, Subsection} from '@/components/Section'; import {Section, Subsection} from '@/components/Section';
import * as Temporal from '@tesseract-design/web-temporal-react'; import * as Temporal from '@tesseract-design/web-temporal-react';
import * as TemporalWip from '@/components/temporal';


const TemporalPage: NextPage = () => { const TemporalPage: NextPage = () => {
return ( return (
@@ -10,6 +11,51 @@ const TemporalPage: NextPage = () => {
<Subsection title="Default"> <Subsection title="Default">
<Temporal.DateDropdown <Temporal.DateDropdown
label="Birthday" label="Birthday"
border
/>
</Subsection>
</Section>
<Section title="TimeSpinner">
<Subsection title="Default">
<TemporalWip.TimeSpinner
label="Time"
variant="default"
border
/>
</Subsection>
<Subsection title="Step">
<TemporalWip.TimeSpinner
label="Time"
variant="default"
border
step="00:15:00"
/>
</Subsection>
<Subsection title="Step + Display Seconds">
<TemporalWip.TimeSpinner
label="Time"
variant="default"
border
step="00:00:05"
displaySeconds
/>
</Subsection>
</Section>
<Section title="YearMonthInput">
<Subsection title="Default">
<TemporalWip.YearMonthInput
label="Month"
variant="default"
border
/>
</Subsection>
</Section>
<Section title="YearWeekInput">
<Subsection title="Default">
<TemporalWip.YearWeekInput
label="Vacation"
variant="default"
border
/> />
</Subsection> </Subsection>
</Section> </Section>


+ 1
- 1
showcases/web-kitchensink-reactnext/tailwind.config.js View File

@@ -6,7 +6,7 @@ module.exports = {
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}', './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}', './src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/categories/**/*.{js,ts,jsx,tsx,mdx}', './src/categories/**/*.{js,ts,jsx,tsx,mdx}',
'./node_modules/@tesseract-design/**/*.{js,ts,jsx,tsx,mdx}',
'./node_modules/@tesseract-design/web-*-react/dist/**/*.js',
], ],
theme: { theme: {
fontFamily: { fontFamily: {


Loading…
Cancel
Save