blob: e6dd867d972c55225a29f9e6829d6468e1886fbc [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { ReactNode, useState, useCallback } from 'react';
import ControlHeader from 'src/explore/components/ControlHeader';
import Select, { SelectProps, OptionsTypePage } from './Select';
export default {
title: 'Select',
component: Select,
};
const DEFAULT_WIDTH = 200;
const options = [
{
label: 'Such an incredibly awesome long long label',
value: 'Such an incredibly awesome long long label',
},
{
label: 'Another incredibly awesome long long label',
value: 'Another incredibly awesome long long label',
},
{ label: 'Just a label', value: 'Just a label' },
{ label: 'A', value: 'A' },
{ label: 'B', value: 'B' },
{ label: 'C', value: 'C' },
{ label: 'D', value: 'D' },
{ label: 'E', value: 'E' },
{ label: 'F', value: 'F' },
{ label: 'G', value: 'G' },
{ label: 'H', value: 'H' },
{ label: 'I', value: 'I' },
];
const selectPositions = [
{
id: 'topLeft',
style: { top: '0', left: '0' },
},
{
id: 'topRight',
style: { top: '0', right: '0' },
},
{
id: 'bottomLeft',
style: { bottom: '0', left: '0' },
},
{
id: 'bottomRight',
style: { bottom: '0', right: '0' },
},
];
const ARG_TYPES = {
options: {
defaultValue: options,
table: {
disable: true,
},
},
ariaLabel: {
table: {
disable: true,
},
},
name: {
table: {
disable: true,
},
},
notFoundContent: {
table: {
disable: true,
},
},
mode: {
defaultValue: 'single',
control: {
type: 'inline-radio',
options: ['single', 'multiple'],
},
},
};
const mountHeader = (type: String) => {
let header;
if (type === 'text') {
header = 'Text header';
} else if (type === 'control') {
header = (
<ControlHeader
label="Control header"
warning="Example of warning messsage"
/>
);
}
return header;
};
export const InteractiveSelect = (args: SelectProps & { header: string }) => (
<div
style={{
width: DEFAULT_WIDTH,
}}
>
<Select {...args} header={mountHeader(args.header)} />
</div>
);
InteractiveSelect.args = {
autoFocus: false,
allowNewOptions: false,
allowClear: false,
showSearch: false,
disabled: false,
invertSelection: false,
placeholder: 'Select ...',
};
InteractiveSelect.argTypes = {
...ARG_TYPES,
header: {
defaultValue: 'none',
control: { type: 'inline-radio', options: ['none', 'text', 'control'] },
},
pageSize: {
table: {
disable: true,
},
},
fetchOnlyOnSearch: {
table: {
disable: true,
},
},
};
InteractiveSelect.story = {
parameters: {
knobs: {
disable: true,
},
},
};
export const AtEveryCorner = () => (
<>
{selectPositions.map(position => (
<div
key={position.id}
style={{
...position.style,
margin: 30,
width: DEFAULT_WIDTH,
position: 'absolute',
}}
>
<Select ariaLabel={`gallery-${position.id}`} options={options} />
</div>
))}
<p style={{ position: 'absolute', top: '40%', left: '33%', width: 500 }}>
The objective of this panel is to show how the Select behaves when in
touch with the viewport extremities. In particular, how the drop-down is
displayed and if the tooltips of truncated items are correctly positioned.
</p>
</>
);
AtEveryCorner.story = {
parameters: {
actions: {
disable: true,
},
controls: {
disable: true,
},
knobs: {
disable: true,
},
},
};
export const PageScroll = () => (
<div style={{ height: 2000, overflowY: 'auto' }}>
<div
style={{
width: DEFAULT_WIDTH,
position: 'absolute',
top: 30,
right: 30,
}}
>
<Select ariaLabel="page-scroll-select-1" options={options} />
</div>
<div
style={{
width: DEFAULT_WIDTH,
position: 'absolute',
bottom: 30,
right: 30,
}}
>
<Select ariaLabel="page-scroll-select-2" options={options} />
</div>
<p
style={{
position: 'absolute',
top: '40%',
left: 30,
width: 500,
}}
>
The objective of this panel is to show how the Select behaves when there's
a scroll on the page. In particular, how the drop-down is displayed.
</p>
</div>
);
PageScroll.story = {
parameters: {
actions: {
disable: true,
},
controls: {
disable: true,
},
knobs: {
disable: true,
},
},
};
const USERS = [
'John',
'Liam',
'Olivia',
'Emma',
'Noah',
'Ava',
'Oliver',
'Elijah',
'Charlotte',
'Diego',
'Evan',
'Michael',
'Giovanni',
'Luca',
'Paolo',
'Francesca',
'Chiara',
'Sara',
'Valentina',
'Jessica',
'Angelica',
'Mario',
'Marco',
'Andrea',
'Luigi',
'Quarto',
'Quinto',
'Sesto',
'Franco',
'Sandro',
'Alehandro',
'Johnny',
'Nikole',
'Igor',
'Sipatha',
'Thami',
'Munei',
'Guilherme',
'Umair',
'Ashfaq',
'Amna',
'Irfan',
'George',
'Naseer',
'Mohammad',
'Rick',
'Saliya',
'Claire',
'Benedetta',
'Ilenia',
];
export const AsyncSelect = ({
withError,
withInitialValue,
responseTime,
...rest
}: SelectProps & {
withError: boolean;
withInitialValue: boolean;
responseTime: number;
}) => {
const [requests, setRequests] = useState<ReactNode[]>([]);
const getResults = (username?: string) => {
let results: { label: string; value: string }[] = [];
if (!username) {
results = USERS.map(u => ({
label: u,
value: u,
}));
} else {
const foundUsers = USERS.filter(u => u.toLowerCase().includes(username));
if (foundUsers) {
results = foundUsers.map(u => ({ label: u, value: u }));
} else {
results = [];
}
}
return results;
};
const setRequestLog = (results: number, total: number, username?: string) => {
const request = (
<>
Emulating network request with search <b>{username || 'empty'}</b> ...{' '}
<b>
{results}/{total}
</b>{' '}
results
</>
);
setRequests(requests => [request, ...requests]);
};
const fetchUserListPage = useCallback(
(
search: string,
page: number,
pageSize: number,
): Promise<OptionsTypePage> => {
const username = search.trim().toLowerCase();
return new Promise(resolve => {
let results = getResults(username);
const totalCount = results.length;
const start = page * pageSize;
const deleteCount =
start + pageSize < totalCount ? pageSize : totalCount - start;
results = results.splice(start, deleteCount);
setRequestLog(start + results.length, totalCount, username);
setTimeout(() => {
resolve({ data: results, totalCount });
}, responseTime * 1000);
});
},
[responseTime],
);
const fetchUserListError = async (): Promise<OptionsTypePage> =>
new Promise((_, reject) => {
reject(new Error('Error while fetching the names from the server'));
});
return (
<>
<div
style={{
width: DEFAULT_WIDTH,
}}
>
<Select
{...rest}
options={withError ? fetchUserListError : fetchUserListPage}
value={
withInitialValue
? { label: 'Valentina', value: 'Valentina' }
: undefined
}
/>
</div>
<div
style={{
position: 'absolute',
top: 32,
left: DEFAULT_WIDTH + 100,
height: 400,
width: 600,
overflowY: 'auto',
border: '1px solid #d9d9d9',
padding: 20,
}}
>
{requests.map((request, index) => (
<p key={`request-${index}`}>{request}</p>
))}
</div>
</>
);
};
AsyncSelect.args = {
allowNewOptions: false,
fetchOnlyOnSearch: false,
pageSize: 10,
withError: false,
withInitialValue: false,
};
AsyncSelect.argTypes = {
...ARG_TYPES,
header: {
table: {
disable: true,
},
},
invertSelection: {
table: {
disable: true,
},
},
pageSize: {
defaultValue: 10,
control: {
type: 'range',
min: 10,
max: 50,
step: 10,
},
},
responseTime: {
defaultValue: 0.5,
name: 'responseTime (seconds)',
control: {
type: 'range',
min: 0.5,
max: 5,
step: 0.5,
},
},
};
AsyncSelect.story = {
parameters: {
knobs: {
disable: true,
},
},
};