mirror of
https://github.com/fergalmoran/turnstone.git
synced 2025-12-22 09:49:56 +00:00
Allow functions for listbox and defaultListbox props
Extend listbox and defaultListbox prop types to allow functions returning a promise resolving to an array of group settings
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
### 1.3.0 (2 Apr 2022)
|
||||||
|
- Extend listbox and defaultListbox prop types to allow functions returning a promise resolving to an array of group settings
|
||||||
|
|
||||||
### 1.2.3 (25 Mar 2022)
|
### 1.2.3 (25 Mar 2022)
|
||||||
- Left align container div contents when focussed
|
- Left align container div contents when focussed
|
||||||
|
|
||||||
|
|||||||
66
README.md
66
README.md
@@ -217,7 +217,7 @@ in order to exit the focused state of the search box.
|
|||||||
- Set to `0` if you want no wait at all (e.g. if your listbox data is not fetched asynchronously)
|
- Set to `0` if you want no wait at all (e.g. if your listbox data is not fetched asynchronously)
|
||||||
|
|
||||||
#### `defaultListbox`
|
#### `defaultListbox`
|
||||||
- Type: `array` or `object`
|
- Type: `array` or `object` or `function`
|
||||||
- Default: `undefined`
|
- Default: `undefined`
|
||||||
- The default listbox is displayed when the search box has focus and is empty.
|
- The default listbox is displayed when the search box has focus and is empty.
|
||||||
- Supply an array if you wish multiple groups of items to appear in the default listbox. Groups can
|
- Supply an array if you wish multiple groups of items to appear in the default listbox. Groups can
|
||||||
@@ -256,6 +256,31 @@ in order to exit the focused state of the search box.
|
|||||||
data: () => fetch(`/api/cities/popular`).then(res => res.json()),
|
data: () => fetch(`/api/cities/popular`).then(res => res.json()),
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
- Supply a function if you wish to dynamically build your listbox contents. One example might be
|
||||||
|
where you have a data source that already groups results such as a GraphQL query. The function must return a promise which resolves to an array structured exactly as detailed above (see "supply an array..."). For example:
|
||||||
|
```jsx
|
||||||
|
(query) => fetch(`/api/default-locations`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(locations => {
|
||||||
|
const {recentSearches, popularCities} = locations
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'Recent Searches',
|
||||||
|
displayField: 'name',
|
||||||
|
data: recentSearches,
|
||||||
|
id: 'recent',
|
||||||
|
ratio: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Popular Cities',
|
||||||
|
displayField: 'name',
|
||||||
|
data: popularCities,
|
||||||
|
id: 'popular',
|
||||||
|
ratio: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
- See the [listbox](#listbox) prop for details on the data structure of groups as these are the same for both `defaultListbox` and `listbox`
|
- See the [listbox](#listbox) prop for details on the data structure of groups as these are the same for both `defaultListbox` and `listbox`
|
||||||
|
|
||||||
#### `defaultListboxIsImmutable`
|
#### `defaultListboxIsImmutable`
|
||||||
@@ -292,7 +317,7 @@ in order to exit the focused state of the search box.
|
|||||||
|
|
||||||
#### `listbox`
|
#### `listbox`
|
||||||
- **Required**
|
- **Required**
|
||||||
- Type: `array` or `object`
|
- Type: `array` or `object` or `function`
|
||||||
- Specifies how listbox results are populated in response to a user's query entered into the search box.
|
- Specifies how listbox results are populated in response to a user's query entered into the search box.
|
||||||
- **Supplying an array**
|
- **Supplying an array**
|
||||||
Supply an array if you wish multiple groups of items to appear in the default listbox. Groups can
|
Supply an array if you wish multiple groups of items to appear in the default listbox. Groups can
|
||||||
@@ -329,12 +354,15 @@ in order to exit the focused state of the search box.
|
|||||||
- The function receives a `query` argument which is a string containing the text entered into the search box. The function would then typically perform a fetch to an API endpoint for matching items and finally formats the data received as required.
|
- The function receives a `query` argument which is a string containing the text entered into the search box. The function would then typically perform a fetch to an API endpoint for matching items and finally formats the data received as required.
|
||||||
- If possible, the function should return enough items to satisfy the `maxItems` prop, in case all of the other groups return zero matches.
|
- If possible, the function should return enough items to satisfy the `maxItems` prop, in case all of the other groups return zero matches.
|
||||||
- See the example above for `data` props supplied as functions.
|
- See the example above for `data` props supplied as functions.
|
||||||
|
- The array returned will not be filtered according to the `searchType`. The presumption is that
|
||||||
|
the function will return an array that is already correctly filtered.
|
||||||
|
|
||||||
**If an array**
|
**If an array**
|
||||||
- Instead of a function, an array of items, matching and non-matching can be supplied and Turnstone filters this down to items that match the query.
|
- Instead of a function, an array of items, matching and non-matching can be supplied and Turnstone filters this down to items that match the query.
|
||||||
- Items can be objects, arrays or strings.
|
- Items can be objects, arrays or strings.
|
||||||
|
- The contents of the array will be filtered down to items matching the user's query based on the `searchType` (see below).
|
||||||
- **`displayField`** (string or number or undefined)
|
- **`displayField`** (string or number or undefined)
|
||||||
- This indicates the field within each item in the data array that contains the text to be displayed in the listbox.
|
- This indicates the field within each item in the data array that contains the text to be displayed in the listbox and the text that will be matched to the user's query.
|
||||||
- If the item is an object or array, `displayField` must be a string or number.
|
- If the item is an object or array, `displayField` must be a string or number.
|
||||||
- If the item is a string, `displayField` can be omitted.
|
- If the item is a string, `displayField` can be omitted.
|
||||||
- **`searchType`** (string)
|
- **`searchType`** (string)
|
||||||
@@ -366,6 +394,38 @@ in order to exit the focused state of the search box.
|
|||||||
- **`displayField`**
|
- **`displayField`**
|
||||||
- **`searchType`**
|
- **`searchType`**
|
||||||
See above for explanations of each field.
|
See above for explanations of each field.
|
||||||
|
- **Supplying a function**
|
||||||
|
Supplying a function is useful if you wish to dynamically build your listbox contents based
|
||||||
|
on the user's query. One example might be where you have a data source that already groups results such as a GraphQL query.
|
||||||
|
The function receives a single string argument representing the user's query entered into the search box
|
||||||
|
The function must return a promise which resolves to an array structured exactly as detailed above in "Supplying an array". For example:
|
||||||
|
```jsx
|
||||||
|
(query) => fetch(`/api/locations?q=${encodeURIComponent(query)}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(locations => {
|
||||||
|
const {cities, airports} = locations
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'cities',
|
||||||
|
name: 'Cities',
|
||||||
|
ratio: 8,
|
||||||
|
displayField: 'name',
|
||||||
|
data: cities,
|
||||||
|
searchType: 'startswith'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'airports',
|
||||||
|
name: 'Airports',
|
||||||
|
ratio: 2,
|
||||||
|
displayField: 'name',
|
||||||
|
data: airports,
|
||||||
|
searchType: 'contains'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
#### `listboxIsImmutable`
|
#### `listboxIsImmutable`
|
||||||
- Type: `boolean`
|
- Type: `boolean`
|
||||||
|
|||||||
@@ -40,6 +40,36 @@ const listbox = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// // UNCOMMENT FOR TESTING LISTBOX PROP SUPPLIED AS A FUNCTION
|
||||||
|
// const listbox = query => {
|
||||||
|
// return fetch(
|
||||||
|
// `${apiHost}/api/search/locations?q=${encodeURIComponent(query)}&limit=${maxItems}`
|
||||||
|
// )
|
||||||
|
// .then(response => response.json())
|
||||||
|
// .then(locations => {
|
||||||
|
// const {cities, airports} = locations
|
||||||
|
|
||||||
|
// return [
|
||||||
|
// {
|
||||||
|
// id: 'cities',
|
||||||
|
// name: 'Cities',
|
||||||
|
// ratio: 8,
|
||||||
|
// displayField: 'name',
|
||||||
|
// data: cities,
|
||||||
|
// searchType: 'startswith'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// id: 'airports',
|
||||||
|
// name: 'Airports',
|
||||||
|
// ratio: 2,
|
||||||
|
// displayField: 'name',
|
||||||
|
// data: airports,
|
||||||
|
// searchType: 'contains'
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [selectedItem, setSelectedItem] = useState(undef)
|
const [selectedItem, setSelectedItem] = useState(undef)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "turnstone",
|
"name": "turnstone",
|
||||||
"version": "1.2.3",
|
"version": "1.3.0",
|
||||||
"description": "React customisable autocomplete component with typeahead and grouped results from multiple APIs.",
|
"description": "React customisable autocomplete component with typeahead and grouped results from multiple APIs.",
|
||||||
"author": "Tom Southall",
|
"author": "Tom Southall",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export default function Container(props) {
|
|||||||
enterKeyHint,
|
enterKeyHint,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
id,
|
id,
|
||||||
|
listbox,
|
||||||
listboxIsImmutable,
|
listboxIsImmutable,
|
||||||
maxItems,
|
maxItems,
|
||||||
minQueryLength,
|
minQueryLength,
|
||||||
@@ -56,11 +57,6 @@ export default function Container(props) {
|
|||||||
Clear
|
Clear
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
// Destructure listbox prop
|
|
||||||
const listbox = Array.isArray(props.listbox)
|
|
||||||
? props.listbox
|
|
||||||
: [{ ...props.listbox, ...{ name: '', ratio: maxItems } }]
|
|
||||||
|
|
||||||
const listboxId = `${id}-listbox`
|
const listboxId = `${id}-listbox`
|
||||||
const errorboxId = `${id}-errorbox`
|
const errorboxId = `${id}-errorbox`
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,16 @@ import firstOfType from 'first-of-type'
|
|||||||
import swrLaggyMiddleware from '../../utils/swrLaggyMiddleware'
|
import swrLaggyMiddleware from '../../utils/swrLaggyMiddleware'
|
||||||
import isUndefined from '../../utils/isUndefined'
|
import isUndefined from '../../utils/isUndefined'
|
||||||
|
|
||||||
|
const convertListboxToFunction = (listbox, maxItems) => {
|
||||||
|
if(typeof listbox === 'function') return listbox
|
||||||
|
|
||||||
|
return () => Promise.resolve(
|
||||||
|
Array.isArray(listbox)
|
||||||
|
? listbox
|
||||||
|
: [{ ...listbox, ...{ name: '', ratio: maxItems } }]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const filterSuppliedData = (group, query) => {
|
const filterSuppliedData = (group, query) => {
|
||||||
const { data, displayField, searchType } = group
|
const { data, displayField, searchType } = group
|
||||||
const caseInsensitiveSearchType = searchType
|
const caseInsensitiveSearchType = searchType
|
||||||
@@ -83,29 +93,32 @@ export const fetcher = (query, listbox, defaultListbox, minQueryLength, maxItems
|
|||||||
|
|
||||||
const isDefaultListbox = (defaultListbox && !query.length)
|
const isDefaultListbox = (defaultListbox && !query.length)
|
||||||
|
|
||||||
const listboxProp = isDefaultListbox ? defaultListbox : listbox
|
const listboxPromise = (convertListboxToFunction(
|
||||||
|
isDefaultListbox ? defaultListbox : listbox,
|
||||||
|
maxItems
|
||||||
|
))(query)
|
||||||
|
|
||||||
const promises = listboxProp.map((group) => {
|
return listboxPromise.then(listboxProp => {
|
||||||
if (typeof group.data === 'function') {
|
const promises = listboxProp.map(
|
||||||
return group.data(query)
|
group => (typeof group.data === 'function')
|
||||||
}
|
? group.data(query)
|
||||||
else {
|
: Promise.resolve(filterSuppliedData(group, query))
|
||||||
return Promise.resolve(filterSuppliedData(group, query))
|
)
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return Promise.all(promises).then((groups) => {
|
return Promise.all(promises).then(groups => {
|
||||||
groups = groups.reduce((prevGroups, group, groupIndex) => {
|
groups = groups.reduce((prevGroups, group, groupIndex) => {
|
||||||
|
const {id: groupId, name: groupName, displayField, searchType} = listboxProp[groupIndex]
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...prevGroups,
|
...prevGroups,
|
||||||
group.map((item) => ({
|
group.map((item) => ({
|
||||||
value: item,
|
value: item,
|
||||||
text: itemText(item, listboxProp[groupIndex].displayField),
|
text: itemText(item, displayField),
|
||||||
groupIndex,
|
groupIndex,
|
||||||
groupId: listboxProp[groupIndex].id,
|
groupId,
|
||||||
groupName: listboxProp[groupIndex].name,
|
groupName,
|
||||||
searchType: listboxProp[groupIndex].searchType,
|
searchType,
|
||||||
displayField: listboxProp[groupIndex].displayField,
|
displayField,
|
||||||
defaultListbox: isDefaultListbox
|
defaultListbox: isDefaultListbox
|
||||||
}))
|
}))
|
||||||
]
|
]
|
||||||
@@ -115,6 +128,7 @@ export const fetcher = (query, listbox, defaultListbox, minQueryLength, maxItems
|
|||||||
|
|
||||||
return groups.flat()
|
return groups.flat()
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const useData = (query, isImmutable, listbox, defaultListbox, minQueryLength, maxItems) => {
|
const useData = (query, isImmutable, listbox, defaultListbox, minQueryLength, maxItems) => {
|
||||||
|
|||||||
@@ -24,10 +24,42 @@ const server = setupServer(
|
|||||||
return res(
|
return res(
|
||||||
ctx.json(fruits.filter(fruit => fruit.toLowerCase().startsWith(q.toLowerCase())))
|
ctx.json(fruits.filter(fruit => fruit.toLowerCase().startsWith(q.toLowerCase())))
|
||||||
)
|
)
|
||||||
|
}),
|
||||||
|
rest.get('http://mock-api-site.com/api/fruits-veg', (req, res, ctx) => {
|
||||||
|
const q = req.url.searchParams.get('q')
|
||||||
|
|
||||||
|
return res(
|
||||||
|
ctx.json({
|
||||||
|
fruits: fruits.filter(fruit => fruit.toLowerCase().startsWith(q.toLowerCase())),
|
||||||
|
veg: vegetables.filter(vegetable => vegetable.toLowerCase().startsWith(q.toLowerCase()))
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const apiListbox = [
|
const item = (props) => {
|
||||||
|
return {
|
||||||
|
...{
|
||||||
|
value: undef,
|
||||||
|
text: undef,
|
||||||
|
groupIndex: undef,
|
||||||
|
groupName: undef,
|
||||||
|
defaultListbox: undef,
|
||||||
|
displayField: undef,
|
||||||
|
groupId: undef,
|
||||||
|
searchType: undef
|
||||||
|
},
|
||||||
|
...props
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiListboxObject = {
|
||||||
|
data: (query) =>
|
||||||
|
fetch(`http://mock-api-site.com/api/fruits?q=${encodeURIComponent(query)}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiListboxArray = [
|
||||||
{
|
{
|
||||||
id: 'books',
|
id: 'books',
|
||||||
name: 'Books',
|
name: 'Books',
|
||||||
@@ -46,7 +78,28 @@ const apiListbox = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
describe('Fetching API data', () => {
|
const apiListboxFunction = query => {
|
||||||
|
return fetch(`http://mock-api-site.com/api/fruits-veg?q=${encodeURIComponent(query)}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(fruitsAndVeg => {
|
||||||
|
const {fruits, veg} = fruitsAndVeg
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'Fruits',
|
||||||
|
ratio: 8,
|
||||||
|
data: fruits
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Vegetables',
|
||||||
|
ratio: 2,
|
||||||
|
data: veg
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Fetching API data using a listbox array', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
server.listen()
|
server.listen()
|
||||||
})
|
})
|
||||||
@@ -56,55 +109,146 @@ describe('Fetching API data', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('Returns expected results', () => {
|
test('Returns expected results', () => {
|
||||||
return fetcher('L', apiListbox, undef, 1, 6).then(items => {
|
return fetcher('L', apiListboxObject, undef, 1, 6).then(items => {
|
||||||
expect(items).toEqual([
|
expect(items).toEqual([
|
||||||
{
|
item({
|
||||||
|
value: 'Legume',
|
||||||
|
text: 'Legume',
|
||||||
|
groupIndex: 0,
|
||||||
|
groupName: '',
|
||||||
|
}),
|
||||||
|
item({
|
||||||
|
value: 'Lemon',
|
||||||
|
text: 'Lemon',
|
||||||
|
groupIndex: 0,
|
||||||
|
groupName: ''
|
||||||
|
}),
|
||||||
|
item({
|
||||||
|
value: 'Lime',
|
||||||
|
text: 'Lime',
|
||||||
|
groupIndex: 0,
|
||||||
|
groupName: ''
|
||||||
|
}),
|
||||||
|
item({
|
||||||
|
value: 'Lychee',
|
||||||
|
text: 'Lychee',
|
||||||
|
groupIndex: 0,
|
||||||
|
groupName: ''
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Fetching API data using a listbox array', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
server.listen()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
server.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Returns expected results', () => {
|
||||||
|
return fetcher('L', apiListboxArray, undef, 1, 6).then(items => {
|
||||||
|
expect(items).toEqual([
|
||||||
|
item({
|
||||||
value: { title: 'Last Argument of Kings', author: 'Joe Abercrombie' },
|
value: { title: 'Last Argument of Kings', author: 'Joe Abercrombie' },
|
||||||
text: 'Last Argument of Kings',
|
text: 'Last Argument of Kings',
|
||||||
groupIndex: 0,
|
groupIndex: 0,
|
||||||
groupName: 'Books',
|
groupName: 'Books',
|
||||||
defaultListbox: undef,
|
|
||||||
displayField: 'title',
|
displayField: 'title',
|
||||||
groupId: 'books',
|
groupId: 'books',
|
||||||
searchType: undef
|
}),
|
||||||
},
|
item({
|
||||||
{
|
|
||||||
value: { title: 'Legend', author: 'Marie Lu' },
|
value: { title: 'Legend', author: 'Marie Lu' },
|
||||||
text: 'Legend',
|
text: 'Legend',
|
||||||
groupIndex: 0,
|
groupIndex: 0,
|
||||||
groupName: 'Books',
|
groupName: 'Books',
|
||||||
defaultListbox: undef,
|
|
||||||
displayField: 'title',
|
displayField: 'title',
|
||||||
groupId: 'books',
|
groupId: 'books',
|
||||||
searchType: undef
|
}),
|
||||||
},
|
item({
|
||||||
{
|
|
||||||
value: { title: 'Life After Life', author: 'Kate Atkinson' },
|
value: { title: 'Life After Life', author: 'Kate Atkinson' },
|
||||||
text: 'Life After Life',
|
text: 'Life After Life',
|
||||||
groupIndex: 0,
|
groupIndex: 0,
|
||||||
groupName: 'Books',
|
groupName: 'Books',
|
||||||
defaultListbox: undef,
|
|
||||||
displayField: 'title',
|
displayField: 'title',
|
||||||
groupId: 'books',
|
groupId: 'books',
|
||||||
searchType: undef
|
}),
|
||||||
},
|
item({
|
||||||
{
|
|
||||||
value: { title: 'Like Water for Chocolate', author: 'Laura Esquivel' },
|
value: { title: 'Like Water for Chocolate', author: 'Laura Esquivel' },
|
||||||
text: 'Like Water for Chocolate',
|
text: 'Like Water for Chocolate',
|
||||||
groupIndex: 0,
|
groupIndex: 0,
|
||||||
groupName: 'Books',
|
groupName: 'Books',
|
||||||
defaultListbox: undef,
|
|
||||||
displayField: 'title',
|
displayField: 'title',
|
||||||
groupId: 'books',
|
groupId: 'books',
|
||||||
searchType: undef
|
}),
|
||||||
},
|
item({
|
||||||
{
|
|
||||||
value: 'Legume',
|
value: 'Legume',
|
||||||
text: 'Legume',
|
text: 'Legume',
|
||||||
groupIndex: 1,
|
groupIndex: 1,
|
||||||
|
groupName: 'Fruits',
|
||||||
|
}),
|
||||||
|
item({
|
||||||
|
value: 'Lemon',
|
||||||
|
text: 'Lemon',
|
||||||
|
groupIndex: 1,
|
||||||
groupName: 'Fruits'
|
groupName: 'Fruits'
|
||||||
},
|
})
|
||||||
{ value: 'Lemon', text: 'Lemon', groupIndex: 1, groupName: 'Fruits' }
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Fetching API data using a listbox function', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
server.listen()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
server.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Returns expected results', () => {
|
||||||
|
return fetcher('L', apiListboxFunction, undef, 1, 6).then(items => {
|
||||||
|
expect(items).toEqual([
|
||||||
|
item({
|
||||||
|
value: 'Legume',
|
||||||
|
text: 'Legume',
|
||||||
|
groupIndex: 0,
|
||||||
|
groupName: 'Fruits',
|
||||||
|
}),
|
||||||
|
item({
|
||||||
|
value: 'Lemon',
|
||||||
|
text: 'Lemon',
|
||||||
|
groupIndex: 0,
|
||||||
|
groupName: 'Fruits'
|
||||||
|
}),
|
||||||
|
item({
|
||||||
|
value: 'Lime',
|
||||||
|
text: 'Lime',
|
||||||
|
groupIndex: 0,
|
||||||
|
groupName: 'Fruits'
|
||||||
|
}),
|
||||||
|
item({
|
||||||
|
value: 'Lychee',
|
||||||
|
text: 'Lychee',
|
||||||
|
groupIndex: 0,
|
||||||
|
groupName: 'Fruits'
|
||||||
|
}),
|
||||||
|
item({
|
||||||
|
value: 'Leek',
|
||||||
|
text: 'Leek',
|
||||||
|
groupIndex: 1,
|
||||||
|
groupName: 'Vegetables'
|
||||||
|
}),
|
||||||
|
item({
|
||||||
|
value: 'Legumes',
|
||||||
|
text: 'Legumes',
|
||||||
|
groupIndex: 1,
|
||||||
|
groupName: 'Vegetables'
|
||||||
|
})
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ const listboxRules = PropTypes.oneOfType([
|
|||||||
]).isRequired,
|
]).isRequired,
|
||||||
searchType: PropTypes.oneOf(searchTypes),
|
searchType: PropTypes.oneOf(searchTypes),
|
||||||
displayField: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
displayField: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||||
})
|
}),
|
||||||
|
PropTypes.func
|
||||||
])
|
])
|
||||||
|
|
||||||
Turnstone.propTypes = {
|
Turnstone.propTypes = {
|
||||||
|
|||||||
Reference in New Issue
Block a user