Browse Source

Add combo box component

Combo box is just ordinary input with options.
pull/1/head
TheoryOfNekomata 1 year ago
parent
commit
004a266dc7
8 changed files with 407 additions and 12 deletions
  1. +5
    -0
      packages/web/base/textcontrol/src/index.ts
  2. +3
    -8
      packages/web/categories/freeform/react/src/components/TextInput/index.tsx
  3. +205
    -0
      packages/web/categories/option/react/src/components/ComboBox/index.tsx
  4. +2
    -2
      packages/web/categories/option/react/src/components/TagInput/index.tsx
  5. +1
    -0
      packages/web/categories/option/react/src/index.ts
  6. +1
    -0
      packages/web/categories/option/react/src/web-option-react.test.ts
  7. +2
    -1
      packages/web/kitchen-sink/react-next/src/components/DefaultLayout/index.tsx
  8. +188
    -1
      packages/web/kitchen-sink/react-next/src/pages/categories/option/index.tsx

+ 5
- 0
packages/web/base/textcontrol/src/index.ts View File

@@ -11,6 +11,11 @@ export enum TextControlStyle {
ALTERNATE = 'alternate',
}

export enum TextControlInputType {
TEXT = 'text',
SEARCH = 'search',
}

export const MIN_HEIGHTS: Record<TextControlSize, string> = {
[TextControlSize.SMALL]: '2.5rem',
[TextControlSize.MEDIUM]: '3rem',


+ 3
- 8
packages/web/categories/freeform/react/src/components/TextInput/index.tsx View File

@@ -1,12 +1,7 @@
import * as React from 'react';
import * as TextControlBase from '@tesseract-design/web-base-textcontrol';

export enum TextInputType {
TEXT = 'text',
SEARCH = 'search',
}

export interface TextInputProps extends Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'type' | 'style' | 'label'> {
export interface TextInputProps extends Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'type' | 'style' | 'label' | 'list'> {
/**
* Short textual description indicating the nature of the component's value.
*/
@@ -34,7 +29,7 @@ export interface TextInputProps extends Omit<React.HTMLProps<HTMLInputElement>,
/**
* Type of the component value.
*/
type?: TextInputType,
type?: TextControlBase.TextControlInputType,
/**
* Style of the component.
*/
@@ -59,7 +54,7 @@ export const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>(
size = TextControlBase.TextControlSize.MEDIUM,
border = false,
block = false,
type = TextInputType.TEXT,
type = TextControlBase.TextControlInputType.TEXT,
style = TextControlBase.TextControlStyle.DEFAULT,
hiddenLabel = false,
className: _className,


+ 205
- 0
packages/web/categories/option/react/src/components/ComboBox/index.tsx View File

@@ -0,0 +1,205 @@
import * as React from 'react';
import * as TextControlBase from '@tesseract-design/web-base-textcontrol';
import * as SelectControlBase from '@tesseract-design/web-base-selectcontrol';
import {useId} from 'react';

export interface ComboBoxProps extends Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'type' | 'style' | 'label' | 'list'> {
/**
* Short textual description indicating the nature of the component's value.
*/
label?: React.ReactNode,
/**
* Short textual description as guidelines for valid input values.
*/
hint?: React.ReactNode,
/**
* Size of the component.
*/
size?: TextControlBase.TextControlSize,
/**
* Additional description, usually graphical, indicating the nature of the component's value.
*/
indicator?: React.ReactNode,
/**
* Should the component display a border?
*/
border?: boolean,
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean,
/**
* Type of the component value.
*/
type?: TextControlBase.TextControlInputType,
/**
* Style of the component.
*/
style?: TextControlBase.TextControlStyle,
/**
* Is the label hidden?
*/
hiddenLabel?: boolean,
/**
* Options available for the component's values.
*/
options?: SelectControlBase.SelectOption[],
}

interface RenderOptionsProps {
options: SelectControlBase.SelectOption[],
optionComponent?: React.ElementType,
optgroupComponent?: React.ElementType,
level?: number,
}

const RenderOptions: React.FC<RenderOptionsProps> = ({
options,
optionComponent: Option = 'option',
optgroupComponent: Optgroup = 'optgroup',
level = 0,
}: RenderOptionsProps) => (
<>
{
options.map((o) => {
if (typeof o.value !== 'undefined') {
return (
<Option
key={`${o.label}:${o.value.toString()}`}
value={o.value}
>
{o.label}
</Option>
);
}

if (typeof o.children !== 'undefined') {
if (level === 0) {
return (
<Optgroup
key={o.label}
label={o.label}
>
<RenderOptions
options={o.children}
optionComponent={Option}
optgroupComponent={Optgroup}
level={level + 1}
/>
</Optgroup>
);
}
return (
<React.Fragment
key={o.label}
>
<Option
disabled
>
{o.label}
</Option>
<RenderOptions
options={o.children}
optionComponent={Option}
optgroupComponent={Optgroup}
level={level + 1}
/>
</React.Fragment>
);
}

return null;
})
}
</>
);

export const ComboBox = React.forwardRef<HTMLInputElement, ComboBoxProps>((
{
label = '',
hint = '',
indicator = null,
size = TextControlBase.TextControlSize.MEDIUM,
border = false,
block = false,
type = TextControlBase.TextControlInputType.TEXT,
style = TextControlBase.TextControlStyle.DEFAULT,
hiddenLabel = false,
className: _className,
placeholder: _placeholder,
as: _as,
options = [],
...etcProps
}: ComboBoxProps,
ref,
) => {
const datalistId = useId();
const textInputBaseArgs: TextControlBase.TextControlBaseArgs = {
block,
border,
size,
indicator: Boolean(indicator),
style,
resizable: false,
predefinedValues: false,
};

return (
<>
<datalist id={datalistId}>
<RenderOptions options={options} />
</datalist>
<div
className={TextControlBase.Root(textInputBaseArgs)}
>
<input
{...etcProps}
className={TextControlBase.Input(textInputBaseArgs)}
ref={ref}
aria-label={label?.toString()}
type={type}
data-testid="input"
list={datalistId}
/>
{
border && (
<span
data-testid="border"
/>
)
}
{
label && !hiddenLabel && (
<div
data-testid="label"
className={TextControlBase.LabelWrapper(textInputBaseArgs)}
>
{label}
</div>
)
}
{hint && (
<div
className={TextControlBase.HintWrapper(textInputBaseArgs)}
data-testid="hint"
>
<div
className={TextControlBase.Hint()}
>
{hint}
</div>
</div>
)}
{indicator && (
<div
className={TextControlBase.IndicatorWrapper(textInputBaseArgs)}
>
{indicator}
</div>
)}
</div>
</>
);
});

ComboBox.displayName = 'ComboBox';

+ 2
- 2
packages/web/categories/option/react/src/components/TagInput/index.tsx View File

@@ -39,7 +39,7 @@ export type TagInputProps = Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'st
separator?: string,
}

export const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(((
export const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((
{
label = '',
hint = '',
@@ -322,6 +322,6 @@ export const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(((
)}
</div>
);
}));
});

TagInput.displayName = 'TagInput';

+ 1
- 0
packages/web/categories/option/react/src/index.ts View File

@@ -1,3 +1,4 @@
export * from './components/ComboBox';
export * from './components/DropdownSelect';
export * from './components/MenuSelect';
export * from './components/RadioButton';


+ 1
- 0
packages/web/categories/option/react/src/web-option-react.test.ts View File

@@ -2,6 +2,7 @@ import * as WebOptionReact from '.';

describe('web-option-react', () => {
it.each([
'ComboBox',
'DropdownSelect',
'MenuSelect',
'RadioButton',


+ 2
- 1
packages/web/kitchen-sink/react-next/src/components/DefaultLayout/index.tsx View File

@@ -1,9 +1,10 @@
import Head from 'next/head';
import { FC } from 'react';
import {FC, ReactNode} from 'react';

type DefaultLayoutProps = {
title?: string,
appName?: string,
children?: ReactNode,
}

export const DefaultLayout: FC<DefaultLayoutProps> = ({


+ 188
- 1
packages/web/kitchen-sink/react-next/src/pages/categories/option/index.tsx View File

@@ -1414,7 +1414,194 @@ const OptionPage: NextPage<Props> = ({
ComboBox
</h1>
<div>
TODO input with datalist
<section>
<h2>
Default
</h2>
<div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Option.ComboBox
size={TextControlSize.SMALL}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
options={options}
/>
</div>
<div>
<Option.ComboBox
border
size={TextControlSize.SMALL}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
options={options}
/>
</div>
<div>
<Option.ComboBox
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
options={options}
/>
</div>
<div>
<Option.ComboBox
border
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
options={options}
/>
</div>
<div>
<Option.ComboBox
size={TextControlSize.LARGE}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
options={options}
/>
</div>
<div>
<Option.ComboBox
border
size={TextControlSize.LARGE}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
options={options}
/>
</div>
<div>
<Option.ComboBox
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
disabled
options={options}
/>
</div>
<div>
<Option.ComboBox
border
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
disabled
options={options}
/>
</div>
</div>
</div>
</section>
<section>
<h2>Alternate</h2>
<div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Option.ComboBox
style={TextControlStyle.ALTERNATE}
size={TextControlSize.SMALL}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
options={options}
/>
</div>
<div>
<Option.ComboBox
border
style={TextControlStyle.ALTERNATE}
size={TextControlSize.SMALL}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
options={options}
/>
</div>
<div>
<Option.ComboBox
style={TextControlStyle.ALTERNATE}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
options={options}
/>
</div>
<div>
<Option.ComboBox
border
style={TextControlStyle.ALTERNATE}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
options={options}
/>
</div>
<div>
<Option.ComboBox
style={TextControlStyle.ALTERNATE}
size={TextControlSize.LARGE}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
options={options}
/>
</div>
<div>
<Option.ComboBox
border
block
style={TextControlStyle.ALTERNATE}
size={TextControlSize.LARGE}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
options={options}
/>
</div>
<div>
<Option.ComboBox
style={TextControlStyle.ALTERNATE}
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
disabled
options={options}
/>
</div>
<div>
<Option.ComboBox
style={TextControlStyle.ALTERNATE}
border
label="MultilineTextInput"
hint="Type anything here&hellip;"
indicator="A"
block
disabled
options={options}
/>
</div>
</div>
</div>
</section>
</div>
</div>
</section>


Loading…
Cancel
Save