From 004a266dc7c86adcad479bcb99a1c872f4001d75 Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Sun, 4 Jun 2023 20:03:31 +0800 Subject: [PATCH] Add combo box component Combo box is just ordinary input with options. --- packages/web/base/textcontrol/src/index.ts | 5 + .../react/src/components/TextInput/index.tsx | 11 +- .../react/src/components/ComboBox/index.tsx | 205 ++++++++++++++++++ .../react/src/components/TagInput/index.tsx | 4 +- .../web/categories/option/react/src/index.ts | 1 + .../option/react/src/web-option-react.test.ts | 1 + .../src/components/DefaultLayout/index.tsx | 3 +- .../src/pages/categories/option/index.tsx | 189 +++++++++++++++- 8 files changed, 407 insertions(+), 12 deletions(-) create mode 100644 packages/web/categories/option/react/src/components/ComboBox/index.tsx diff --git a/packages/web/base/textcontrol/src/index.ts b/packages/web/base/textcontrol/src/index.ts index 4591cb6..c248933 100644 --- a/packages/web/base/textcontrol/src/index.ts +++ b/packages/web/base/textcontrol/src/index.ts @@ -11,6 +11,11 @@ export enum TextControlStyle { ALTERNATE = 'alternate', } +export enum TextControlInputType { + TEXT = 'text', + SEARCH = 'search', +} + export const MIN_HEIGHTS: Record = { [TextControlSize.SMALL]: '2.5rem', [TextControlSize.MEDIUM]: '3rem', diff --git a/packages/web/categories/freeform/react/src/components/TextInput/index.tsx b/packages/web/categories/freeform/react/src/components/TextInput/index.tsx index 7af7888..0c0c701 100644 --- a/packages/web/categories/freeform/react/src/components/TextInput/index.tsx +++ b/packages/web/categories/freeform/react/src/components/TextInput/index.tsx @@ -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, 'size' | 'type' | 'style' | 'label'> { +export interface TextInputProps extends Omit, '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, /** * Type of the component value. */ - type?: TextInputType, + type?: TextControlBase.TextControlInputType, /** * Style of the component. */ @@ -59,7 +54,7 @@ export const TextInput = React.forwardRef( size = TextControlBase.TextControlSize.MEDIUM, border = false, block = false, - type = TextInputType.TEXT, + type = TextControlBase.TextControlInputType.TEXT, style = TextControlBase.TextControlStyle.DEFAULT, hiddenLabel = false, className: _className, diff --git a/packages/web/categories/option/react/src/components/ComboBox/index.tsx b/packages/web/categories/option/react/src/components/ComboBox/index.tsx new file mode 100644 index 0000000..11fdcdd --- /dev/null +++ b/packages/web/categories/option/react/src/components/ComboBox/index.tsx @@ -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, '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 = ({ + options, + optionComponent: Option = 'option', + optgroupComponent: Optgroup = 'optgroup', + level = 0, +}: RenderOptionsProps) => ( + <> + { + options.map((o) => { + if (typeof o.value !== 'undefined') { + return ( + + ); + } + + if (typeof o.children !== 'undefined') { + if (level === 0) { + return ( + + + + ); + } + return ( + + + + + ); + } + + return null; + }) + } + +); + +export const ComboBox = React.forwardRef(( + { + 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 ( + <> + + + +
+ + { + border && ( + + ) + } + { + label && !hiddenLabel && ( +
+ {label} +
+ ) + } + {hint && ( +
+
+ {hint} +
+
+ )} + {indicator && ( +
+ {indicator} +
+ )} +
+ + ); +}); + +ComboBox.displayName = 'ComboBox'; diff --git a/packages/web/categories/option/react/src/components/TagInput/index.tsx b/packages/web/categories/option/react/src/components/TagInput/index.tsx index 8c816f3..ab3e28c 100644 --- a/packages/web/categories/option/react/src/components/TagInput/index.tsx +++ b/packages/web/categories/option/react/src/components/TagInput/index.tsx @@ -39,7 +39,7 @@ export type TagInputProps = Omit, 'size' | 'st separator?: string, } -export const TagInput = React.forwardRef((( +export const TagInput = React.forwardRef(( { label = '', hint = '', @@ -322,6 +322,6 @@ export const TagInput = React.forwardRef((( )} ); -})); +}); TagInput.displayName = 'TagInput'; diff --git a/packages/web/categories/option/react/src/index.ts b/packages/web/categories/option/react/src/index.ts index 32e630e..0d8a157 100644 --- a/packages/web/categories/option/react/src/index.ts +++ b/packages/web/categories/option/react/src/index.ts @@ -1,3 +1,4 @@ +export * from './components/ComboBox'; export * from './components/DropdownSelect'; export * from './components/MenuSelect'; export * from './components/RadioButton'; diff --git a/packages/web/categories/option/react/src/web-option-react.test.ts b/packages/web/categories/option/react/src/web-option-react.test.ts index 50e817a..fba8237 100644 --- a/packages/web/categories/option/react/src/web-option-react.test.ts +++ b/packages/web/categories/option/react/src/web-option-react.test.ts @@ -2,6 +2,7 @@ import * as WebOptionReact from '.'; describe('web-option-react', () => { it.each([ + 'ComboBox', 'DropdownSelect', 'MenuSelect', 'RadioButton', diff --git a/packages/web/kitchen-sink/react-next/src/components/DefaultLayout/index.tsx b/packages/web/kitchen-sink/react-next/src/components/DefaultLayout/index.tsx index 26effee..b5f3caf 100644 --- a/packages/web/kitchen-sink/react-next/src/components/DefaultLayout/index.tsx +++ b/packages/web/kitchen-sink/react-next/src/components/DefaultLayout/index.tsx @@ -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 = ({ diff --git a/packages/web/kitchen-sink/react-next/src/pages/categories/option/index.tsx b/packages/web/kitchen-sink/react-next/src/pages/categories/option/index.tsx index b131df2..5da805d 100644 --- a/packages/web/kitchen-sink/react-next/src/pages/categories/option/index.tsx +++ b/packages/web/kitchen-sink/react-next/src/pages/categories/option/index.tsx @@ -1414,7 +1414,194 @@ const OptionPage: NextPage = ({ ComboBox
- TODO input with datalist +
+

+ Default +

+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+

Alternate

+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+