mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
New Select: Initial scaffolding (#89114)
* Initial scaffolding * Extend props from Input * Rename to Combobox * Use search icon * Remove use of SelectableValue * Remove unused import * Memoize
This commit is contained in:
parent
afcb5a855c
commit
5e2f08de31
@ -66,6 +66,7 @@
|
||||
"classnames": "2.5.1",
|
||||
"d3": "7.9.0",
|
||||
"date-fns": "3.6.0",
|
||||
"downshift": "^9.0.6",
|
||||
"hoist-non-react-statics": "3.3.2",
|
||||
"i18next": "^23.0.0",
|
||||
"i18next-browser-languagedetector": "^7.0.2",
|
||||
|
@ -0,0 +1,47 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { Meta, StoryFn } from '@storybook/react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { Combobox } from './Combobox';
|
||||
|
||||
const meta: Meta<typeof Combobox> = {
|
||||
title: 'Forms/Combobox',
|
||||
component: Combobox,
|
||||
args: {
|
||||
loading: undefined,
|
||||
invalid: undefined,
|
||||
placeholder: 'Select an option...',
|
||||
options: [
|
||||
{ label: 'Apple', value: 'apple' },
|
||||
{ label: 'Banana', value: 'banana' },
|
||||
{ label: 'Carrot', value: 'carrot' },
|
||||
{ label: 'Dill', value: 'dill' },
|
||||
{ label: 'Eggplant', value: 'eggplant' },
|
||||
{ label: 'Fennel', value: 'fennel' },
|
||||
{ label: 'Grape', value: 'grape' },
|
||||
{ label: 'Honeydew', value: 'honeydew' },
|
||||
{ label: 'Iceberg Lettuce', value: 'iceberg-lettuce' },
|
||||
{ label: 'Jackfruit', value: 'jackfruit' },
|
||||
{ label: '1', value: 1 },
|
||||
{ label: '2', value: 2 },
|
||||
{ label: '3', value: 3 },
|
||||
],
|
||||
value: 'banana',
|
||||
},
|
||||
};
|
||||
|
||||
export const Basic: StoryFn<typeof Combobox> = (args) => {
|
||||
const [value, setValue] = useState(args.value);
|
||||
return (
|
||||
<Combobox
|
||||
{...args}
|
||||
value={value}
|
||||
onChange={(val) => {
|
||||
setValue(val.value);
|
||||
action('onChange')(val);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default meta;
|
64
packages/grafana-ui/src/components/Combobox/Combobox.tsx
Normal file
64
packages/grafana-ui/src/components/Combobox/Combobox.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import { useCombobox } from 'downshift';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { Input, Props as InputProps } from '../Input/Input';
|
||||
|
||||
type Value = string | number;
|
||||
type Option = {
|
||||
label: string;
|
||||
value: Value;
|
||||
};
|
||||
|
||||
interface ComboboxProps
|
||||
extends Omit<InputProps, 'width' | 'prefix' | 'suffix' | 'value' | 'addonBefore' | 'addonAfter' | 'onChange'> {
|
||||
onChange: (val: Option) => void;
|
||||
value: Value;
|
||||
options: Option[];
|
||||
}
|
||||
|
||||
function itemToString(item: Option | null) {
|
||||
return item?.label || '';
|
||||
}
|
||||
|
||||
function itemFilter(inputValue: string) {
|
||||
const lowerCasedInputValue = inputValue.toLowerCase();
|
||||
|
||||
return (item: Option) => {
|
||||
return (
|
||||
!inputValue ||
|
||||
item?.label?.toLowerCase().includes(lowerCasedInputValue) ||
|
||||
item?.value?.toString().toLowerCase().includes(lowerCasedInputValue)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export const Combobox = ({ options, onChange, value, ...restProps }: ComboboxProps) => {
|
||||
const [items, setItems] = useState(options);
|
||||
const selectedItem = useMemo(() => options.find((option) => option.value === value) || null, [options, value]);
|
||||
|
||||
const { getInputProps, getMenuProps, getItemProps, isOpen } = useCombobox({
|
||||
items,
|
||||
itemToString,
|
||||
selectedItem,
|
||||
onInputValueChange: ({ inputValue }) => {
|
||||
setItems(options.filter(itemFilter(inputValue)));
|
||||
},
|
||||
onSelectedItemChange: ({ selectedItem }) => onChange(selectedItem),
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<Input suffix={<Icon name={isOpen ? 'search' : 'angle-down'} />} {...restProps} {...getInputProps()} />
|
||||
<ul {...getMenuProps()}>
|
||||
{isOpen &&
|
||||
items.map((item, index) => {
|
||||
return (
|
||||
<li key={item.value} {...getItemProps({ item, index })}>
|
||||
{item.label}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
27
yarn.lock
27
yarn.lock
@ -3684,6 +3684,7 @@ __metadata:
|
||||
csstype: "npm:3.1.3"
|
||||
d3: "npm:7.9.0"
|
||||
date-fns: "npm:3.6.0"
|
||||
downshift: "npm:^9.0.6"
|
||||
esbuild: "npm:0.20.2"
|
||||
expose-loader: "npm:5.0.0"
|
||||
hoist-non-react-statics: "npm:3.3.2"
|
||||
@ -12460,6 +12461,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"compute-scroll-into-view@npm:^3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "compute-scroll-into-view@npm:3.1.0"
|
||||
checksum: 10/cc5211d49bced5ad23385da5c2eaf69b6045628581b0dcb9f4dd407bfee51bbd26d2bce426be26edf2feaf8c243706f5a7c3759827d89cc5a01a5cf7d299a5eb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"concat-map@npm:0.0.1":
|
||||
version: 0.0.1
|
||||
resolution: "concat-map@npm:0.0.1"
|
||||
@ -14380,6 +14388,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"downshift@npm:^9.0.6":
|
||||
version: 9.0.6
|
||||
resolution: "downshift@npm:9.0.6"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.24.5"
|
||||
compute-scroll-into-view: "npm:^3.1.0"
|
||||
prop-types: "npm:^15.8.1"
|
||||
react-is: "npm:18.2.0"
|
||||
tslib: "npm:^2.6.2"
|
||||
peerDependencies:
|
||||
react: ">=16.12.0"
|
||||
checksum: 10/e84ceba61429694395e6c2ab7213e76d6807a87ceb52b0db08642120ac6d6affc74426772431df29741d571743143316a11e6a815280bed4bbc1113cd83849b1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"duplexer@npm:^0.1.1, duplexer@npm:^0.1.2":
|
||||
version: 0.1.2
|
||||
resolution: "duplexer@npm:0.1.2"
|
||||
@ -25292,7 +25315,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-is@npm:^16.12.0 || ^17.0.0 || ^18.0.0, react-is@npm:^18.0.0, react-is@npm:^18.2.0":
|
||||
"react-is@npm:18.2.0, react-is@npm:^16.12.0 || ^17.0.0 || ^18.0.0, react-is@npm:^18.0.0, react-is@npm:^18.2.0":
|
||||
version: 18.2.0
|
||||
resolution: "react-is@npm:18.2.0"
|
||||
checksum: 10/200cd65bf2e0be7ba6055f647091b725a45dd2a6abef03bf2380ce701fd5edccee40b49b9d15edab7ac08a762bf83cb4081e31ec2673a5bfb549a36ba21570df
|
||||
@ -29156,7 +29179,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tslib@npm:2.6.3, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.4.1":
|
||||
"tslib@npm:2.6.3, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.4.1, tslib@npm:^2.6.2":
|
||||
version: 2.6.3
|
||||
resolution: "tslib@npm:2.6.3"
|
||||
checksum: 10/52109bb681f8133a2e58142f11a50e05476de4f075ca906d13b596ae5f7f12d30c482feb0bff167ae01cfc84c5803e575a307d47938999246f5a49d174fc558c
|
||||
|
Loading…
Reference in New Issue
Block a user