@indico-data/design-system 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +27 -0
- package/.eslintignore +6 -0
- package/.eslintrc.js +63 -0
- package/.husky/pre-commit +4 -0
- package/.prettierignore +3 -0
- package/.prettierrc +6 -0
- package/.stackblitzrc +4 -0
- package/.storybook/indico-data-logo.svg +1 -0
- package/.storybook/main.ts +36 -0
- package/.storybook/preview-head.html +19 -0
- package/.storybook/preview.ts +24 -0
- package/.storybook/themes.js +24 -0
- package/.yarn/releases/yarn-classic.cjs +179386 -0
- package/.yarnrc.yml +1 -0
- package/README.md +30 -0
- package/package.json +79 -0
- package/src/components/Accordion/Accordion.stories.tsx +47 -0
- package/src/components/Accordion/Accordion.styles.ts +35 -0
- package/src/components/Accordion/Accordion.tsx +30 -0
- package/src/components/Accordion/index.ts +1 -0
- package/src/components/Icon/Icon.stories.tsx +60 -0
- package/src/components/Icon/Icon.tsx +75 -0
- package/src/components/Icon/faIcons.tsx +168 -0
- package/src/components/Icon/index.ts +2 -0
- package/src/components/Icon/indicons.tsx +699 -0
- package/src/components/Icon/storyHelpers.tsx +87 -0
- package/src/components/ListTable/Header/Header.styles.ts +62 -0
- package/src/components/ListTable/Header/Header.tsx +67 -0
- package/src/components/ListTable/Header/index.ts +1 -0
- package/src/components/ListTable/ListTable.stories.tsx +301 -0
- package/src/components/ListTable/ListTable.styles.ts +76 -0
- package/src/components/ListTable/ListTable.tsx +135 -0
- package/src/components/ListTable/index.ts +1 -0
- package/src/components/ListTable/mock-data/index.ts +1 -0
- package/src/components/ListTable/mock-data/mock-data.ts +291 -0
- package/src/components/Pagination/Pagination.stories.tsx +45 -0
- package/src/components/Pagination/Pagination.styles.ts +51 -0
- package/src/components/Pagination/Pagination.tsx +118 -0
- package/src/components/Pagination/index.ts +1 -0
- package/src/components/basic-section/Section/Section.stories.tsx +14 -0
- package/src/components/basic-section/Section/Section.styles.ts +8 -0
- package/src/components/basic-section/Section/Section.tsx +30 -0
- package/src/components/basic-section/Section/index.ts +1 -0
- package/src/components/basic-section/SectionBlock/SectionBlock.styles.ts +15 -0
- package/src/components/basic-section/SectionBlock/SectionBlock.tsx +37 -0
- package/src/components/basic-section/SectionBlock/index.ts +1 -0
- package/src/components/basic-section/SectionBody/SectionBody.stories.tsx +16 -0
- package/src/components/basic-section/SectionBody/SectionBody.styles.ts +18 -0
- package/src/components/basic-section/SectionBody/SectionBody.tsx +30 -0
- package/src/components/basic-section/SectionBody/index.ts +1 -0
- package/src/components/basic-section/SectionHeader/SectionHeader.stories.tsx +17 -0
- package/src/components/basic-section/SectionHeader/SectionHeader.styles.ts +5 -0
- package/src/components/basic-section/SectionHeader/SectionHeader.tsx +35 -0
- package/src/components/basic-section/SectionHeader/index.ts +1 -0
- package/src/components/basic-section/SectionTable/SectionTable.styles.ts +237 -0
- package/src/components/basic-section/SectionTable/SectionTable.tsx +229 -0
- package/src/components/basic-section/SectionTable/index.ts +1 -0
- package/src/components/basic-section/index.ts +5 -0
- package/src/components/buttons/Button/Button.stories.tsx +80 -0
- package/src/components/buttons/Button/Button.styles.ts +99 -0
- package/src/components/buttons/Button/Button.tsx +74 -0
- package/src/components/buttons/Button/index.ts +1 -0
- package/src/components/buttons/IconButton/IconButton.stories.tsx +96 -0
- package/src/components/buttons/IconButton/IconButton.styles.ts +78 -0
- package/src/components/buttons/IconButton/IconButton.tsx +109 -0
- package/src/components/buttons/IconButton/index.ts +1 -0
- package/src/components/buttons/commonStyles.ts +108 -0
- package/src/components/buttons/index.ts +2 -0
- package/src/components/buttons/types.ts +2 -0
- package/src/components/dropdowns/BorderSelect/BorderSelect.stories.tsx +22 -0
- package/src/components/dropdowns/BorderSelect/BorderSelect.styles.ts +73 -0
- package/src/components/dropdowns/BorderSelect/BorderSelect.tsx +85 -0
- package/src/components/dropdowns/BorderSelect/index.ts +1 -0
- package/src/components/dropdowns/MultiCombobox/MultiCombobox.stories.tsx +146 -0
- package/src/components/dropdowns/MultiCombobox/MultiCombobox.styles.ts +89 -0
- package/src/components/dropdowns/MultiCombobox/MultiCombobox.tsx +123 -0
- package/src/components/dropdowns/MultiCombobox/index.ts +1 -0
- package/src/components/dropdowns/Select/Select.stories.tsx +54 -0
- package/src/components/dropdowns/Select/Select.styles.ts +73 -0
- package/src/components/dropdowns/Select/Select.tsx +69 -0
- package/src/components/dropdowns/Select/index.ts +1 -0
- package/src/components/dropdowns/SingleCombobox/SingleCombobox.stories.tsx +61 -0
- package/src/components/dropdowns/SingleCombobox/SingleCombobox.styles.ts +56 -0
- package/src/components/dropdowns/SingleCombobox/SingleCombobox.tsx +103 -0
- package/src/components/dropdowns/SingleCombobox/index.ts +1 -0
- package/src/components/dropdowns/commonStyles.ts +65 -0
- package/src/components/dropdowns/index.ts +4 -0
- package/src/components/dropdowns/types.ts +45 -0
- package/src/components/dropdowns/useCombobox.ts +32 -0
- package/src/components/dropdowns/utils.tsx +25 -0
- package/src/components/index.ts +9 -0
- package/src/components/inputs/EditableInput/EditableInput.stories.tsx +26 -0
- package/src/components/inputs/EditableInput/EditableInput.styles.ts +21 -0
- package/src/components/inputs/EditableInput/EditableInput.tsx +103 -0
- package/src/components/inputs/EditableInput/index.ts +1 -0
- package/src/components/inputs/NumberInput/NumberInput.stories.tsx +72 -0
- package/src/components/inputs/NumberInput/NumberInput.styles.ts +66 -0
- package/src/components/inputs/NumberInput/NumberInput.tsx +153 -0
- package/src/components/inputs/NumberInput/index.ts +1 -0
- package/src/components/inputs/SearchInput/SearchInput.stories.tsx +17 -0
- package/src/components/inputs/SearchInput/SearchInput.styles.ts +25 -0
- package/src/components/inputs/SearchInput/SearchInput.tsx +47 -0
- package/src/components/inputs/SearchInput/index.ts +1 -0
- package/src/components/inputs/TextInput/TextInput.stories.tsx +104 -0
- package/src/components/inputs/TextInput/TextInput.styles.ts +74 -0
- package/src/components/inputs/TextInput/TextInput.tsx +116 -0
- package/src/components/inputs/TextInput/index.ts +1 -0
- package/src/components/inputs/index.ts +4 -0
- package/src/components/inputs/inputsCommon.styles.ts +61 -0
- package/src/components/loading-indicators/BarSpinner/BarSpinner.stories.tsx +14 -0
- package/src/components/loading-indicators/BarSpinner/BarSpinner.styles.ts +53 -0
- package/src/components/loading-indicators/BarSpinner/BarSpinner.tsx +21 -0
- package/src/components/loading-indicators/BarSpinner/index.ts +1 -0
- package/src/components/loading-indicators/CirclePulse/CirclePulse.stories.tsx +22 -0
- package/src/components/loading-indicators/CirclePulse/CirclePulse.styles.ts +81 -0
- package/src/components/loading-indicators/CirclePulse/CirclePulse.tsx +61 -0
- package/src/components/loading-indicators/CirclePulse/index.ts +1 -0
- package/src/components/loading-indicators/CircleSpinner/CircleSpinner.stories.tsx +16 -0
- package/src/components/loading-indicators/CircleSpinner/CircleSpinner.tsx +37 -0
- package/src/components/loading-indicators/CircleSpinner/index.ts +1 -0
- package/src/components/loading-indicators/LoadingList/LoadingList.stories.tsx +14 -0
- package/src/components/loading-indicators/LoadingList/LoadingList.styles.ts +42 -0
- package/src/components/loading-indicators/LoadingList/LoadingList.tsx +9 -0
- package/src/components/loading-indicators/LoadingList/index.ts +1 -0
- package/src/components/loading-indicators/PercentageRing/PercentageRing.stories.tsx +18 -0
- package/src/components/loading-indicators/PercentageRing/PercentageRing.styles.ts +27 -0
- package/src/components/loading-indicators/PercentageRing/PercentageRing.tsx +76 -0
- package/src/components/loading-indicators/PercentageRing/index.ts +1 -0
- package/src/components/loading-indicators/RandomLoadingMessage/RandomLoadingMessage.stories.tsx +16 -0
- package/src/components/loading-indicators/RandomLoadingMessage/RandomLoadingMessage.tsx +18 -0
- package/src/components/loading-indicators/RandomLoadingMessage/index.ts +1 -0
- package/src/components/loading-indicators/RandomLoadingMessage/random-messages.js +67 -0
- package/src/components/loading-indicators/index.ts +6 -0
- package/src/components/user-feedback/Shrug/Shrug.stories.tsx +38 -0
- package/src/components/user-feedback/Shrug/Shrug.styles.ts +23 -0
- package/src/components/user-feedback/Shrug/Shrug.tsx +44 -0
- package/src/components/user-feedback/Shrug/index.ts +1 -0
- package/src/components/user-feedback/index.ts +1 -0
- package/src/index.tsx +18 -0
- package/src/styles/globals/buttons.ts +154 -0
- package/src/styles/globals/forms.ts +103 -0
- package/src/styles/globals/index.tsx +25 -0
- package/src/styles/globals/layout.ts +25 -0
- package/src/styles/globals/lists.ts +23 -0
- package/src/styles/globals/margin-padding.ts +33 -0
- package/src/styles/globals/media.ts +13 -0
- package/src/styles/globals/tables.ts +34 -0
- package/src/styles/globals/typography.ts +95 -0
- package/src/styles/globals/utility-classes.ts +76 -0
- package/src/tokens/animation.ts +6 -0
- package/src/tokens/breakpoints.ts +11 -0
- package/src/tokens/colors.ts +279 -0
- package/src/tokens/index.ts +20 -0
- package/src/tokens/margin.ts +5 -0
- package/src/tokens/numbers.js +41 -0
- package/src/tokens/padding.ts +5 -0
- package/src/tokens/spacings.ts +5 -0
- package/src/tokens/typography.ts +37 -0
- package/src/types.ts +6 -0
- package/tsconfig.json +13 -0
- package/webpack.config.js +35 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { v4 as uuid } from 'uuid';
|
|
4
|
+
|
|
5
|
+
import { Icon } from '@/components';
|
|
6
|
+
import { PermafrostComponent } from '@/types';
|
|
7
|
+
|
|
8
|
+
import { StyledSelect } from './Select.styles';
|
|
9
|
+
import classNames from 'classnames';
|
|
10
|
+
import { ComboboxSize, ComboboxVariant } from '../types';
|
|
11
|
+
|
|
12
|
+
type Props = PermafrostComponent & {
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
initialText?: string;
|
|
15
|
+
options: { name: string; value: string | number }[];
|
|
16
|
+
size?: ComboboxSize;
|
|
17
|
+
style?: object;
|
|
18
|
+
value?: string | number;
|
|
19
|
+
variant?: ComboboxVariant;
|
|
20
|
+
onChange(e: React.ChangeEvent<HTMLSelectElement>): void;
|
|
21
|
+
onClick?(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const Select = (props: Props) => {
|
|
25
|
+
const {
|
|
26
|
+
id,
|
|
27
|
+
className,
|
|
28
|
+
disabled,
|
|
29
|
+
initialText,
|
|
30
|
+
onChange,
|
|
31
|
+
options,
|
|
32
|
+
size = 'medium',
|
|
33
|
+
style = {},
|
|
34
|
+
value,
|
|
35
|
+
variant,
|
|
36
|
+
onClick,
|
|
37
|
+
} = props;
|
|
38
|
+
|
|
39
|
+
const selectId = uuid();
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<StyledSelect className={className} data-cy={props['data-cy']} id={id} style={style}>
|
|
43
|
+
<div
|
|
44
|
+
className={classNames('Select__container', size, variant)}
|
|
45
|
+
onClick={(e) => (onClick ? onClick(e) : null)}
|
|
46
|
+
>
|
|
47
|
+
<select onChange={onChange} value={value} disabled={disabled} id={selectId}>
|
|
48
|
+
{initialText && (
|
|
49
|
+
<option disabled value="">
|
|
50
|
+
{initialText}
|
|
51
|
+
</option>
|
|
52
|
+
)}
|
|
53
|
+
|
|
54
|
+
{options.map((o) => {
|
|
55
|
+
const key = uuid();
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<option key={key} value={o.value}>
|
|
59
|
+
{o.name}
|
|
60
|
+
</option>
|
|
61
|
+
);
|
|
62
|
+
})}
|
|
63
|
+
</select>
|
|
64
|
+
|
|
65
|
+
<Icon className="chevron" name="chevron-down" size={size === 'small' ? [10] : [12]} />
|
|
66
|
+
</div>
|
|
67
|
+
</StyledSelect>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Select } from './Select';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// TODO: This component's migration was fast-tracked for Insights. Assess for potential refactor and documentation.
|
|
2
|
+
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
|
|
5
|
+
import { ComboboxOption } from '../types';
|
|
6
|
+
import { SingleCombobox } from './SingleCombobox';
|
|
7
|
+
|
|
8
|
+
const options = [
|
|
9
|
+
{ value: 'due-date', label: 'Due Date' },
|
|
10
|
+
{ value: 'invoice-number', label: 'Invoice Number' },
|
|
11
|
+
{ value: 'invoice', label: 'Invoice' },
|
|
12
|
+
{ value: 'line-item-value', label: 'Line Item Value' },
|
|
13
|
+
{ value: 'line-item', label: 'Line Item' },
|
|
14
|
+
{ value: 'vendor', label: 'Vendor' },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const meta = {
|
|
18
|
+
component: SingleCombobox,
|
|
19
|
+
title: 'dropdowns/SingleCombobox',
|
|
20
|
+
argTypes: {},
|
|
21
|
+
args: {
|
|
22
|
+
options,
|
|
23
|
+
placeholder: 'Select Label to add to Label Group',
|
|
24
|
+
onChange: (selectedOption: ComboboxOption) => console.info(selectedOption),
|
|
25
|
+
},
|
|
26
|
+
} satisfies Meta<typeof SingleCombobox>;
|
|
27
|
+
|
|
28
|
+
export default meta;
|
|
29
|
+
type Story = StoryObj<typeof SingleCombobox>;
|
|
30
|
+
|
|
31
|
+
export const Normal: Story = {
|
|
32
|
+
args: {
|
|
33
|
+
dropdownIndicatorProps: {
|
|
34
|
+
'data-cy': 'dropdownIndicatorSelector',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const Open: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
menuIsOpen: true,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const Disabled: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
disabled: true,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const WithDataCyAttributes: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
'data-cy': 'cypressSelector',
|
|
54
|
+
dropdownIndicatorProps: {
|
|
55
|
+
'data-cy': 'dropdownIndicatorSelector',
|
|
56
|
+
},
|
|
57
|
+
optionProps: {
|
|
58
|
+
'data-cy': 'optionSelector',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
import ReactSelect from 'react-select';
|
|
4
|
+
|
|
5
|
+
import { COLORS } from '@/tokens';
|
|
6
|
+
|
|
7
|
+
import { basicDefaults } from '../commonStyles';
|
|
8
|
+
|
|
9
|
+
export const StyledSingleCombobox = styled(ReactSelect)`
|
|
10
|
+
${basicDefaults};
|
|
11
|
+
|
|
12
|
+
// overall component container
|
|
13
|
+
.combobox__control {
|
|
14
|
+
border-radius: 0;
|
|
15
|
+
|
|
16
|
+
background-color: transparent;
|
|
17
|
+
|
|
18
|
+
border-top: none;
|
|
19
|
+
border-right: none;
|
|
20
|
+
border-bottom: 1px solid ${COLORS.defaultFontColor};
|
|
21
|
+
border-left: none;
|
|
22
|
+
|
|
23
|
+
:hover {
|
|
24
|
+
border-color: ${COLORS.lightFontColor};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.combobox__control--is-disabled {
|
|
29
|
+
:hover {
|
|
30
|
+
border-color: ${COLORS.defaultFontColor};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// the dropdown list container
|
|
35
|
+
.combobox__menu {
|
|
36
|
+
background-color: ${COLORS.clay};
|
|
37
|
+
border: 1px solid ${COLORS.mediumGray};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// the dropdown list
|
|
41
|
+
.combobox__menu-list {
|
|
42
|
+
border-radius: inherit;
|
|
43
|
+
padding-top: 0;
|
|
44
|
+
padding-bottom: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// text input placeholder text
|
|
48
|
+
.combobox__placeholder {
|
|
49
|
+
color: ${COLORS.defaultFontColor};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// selected item text inside input
|
|
53
|
+
.combobox__single-value {
|
|
54
|
+
color: ${COLORS.lightFontColor};
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { components } from 'react-select';
|
|
4
|
+
|
|
5
|
+
import { Icon } from '@/components';
|
|
6
|
+
import { PermafrostComponent } from '@/types';
|
|
7
|
+
import { addCustomProps } from '../utils';
|
|
8
|
+
|
|
9
|
+
import { StyledSingleCombobox } from './SingleCombobox.styles';
|
|
10
|
+
|
|
11
|
+
import type { ComboboxProps, ComboboxOption } from '../types';
|
|
12
|
+
|
|
13
|
+
type Props = PermafrostComponent &
|
|
14
|
+
ComboboxProps & {
|
|
15
|
+
dropdownIndicatorProps?: {
|
|
16
|
+
'data-cy': string;
|
|
17
|
+
};
|
|
18
|
+
optionProps?: {
|
|
19
|
+
'data-cy': string;
|
|
20
|
+
};
|
|
21
|
+
// validationErrors?: string[];
|
|
22
|
+
value?: ComboboxOption;
|
|
23
|
+
onChange: (selectedOption: ComboboxOption) => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Combobox component to select a single option. If selections are not bound to
|
|
28
|
+
* outside state via the `value` prop, you will need to import and use the
|
|
29
|
+
* `useCombobox` hook in order to clear its selected value.
|
|
30
|
+
*
|
|
31
|
+
* @see [useCombobox]{@link import('useCombobox').useCombobox}
|
|
32
|
+
*/
|
|
33
|
+
export const SingleCombobox = React.forwardRef((props: Props, ref: any) => {
|
|
34
|
+
const {
|
|
35
|
+
className,
|
|
36
|
+
closeMenuOnSelect,
|
|
37
|
+
customOptionLabel,
|
|
38
|
+
customOptionValue,
|
|
39
|
+
defaultValue,
|
|
40
|
+
disabled,
|
|
41
|
+
dropdownIndicatorProps = {},
|
|
42
|
+
id,
|
|
43
|
+
menuIsOpen,
|
|
44
|
+
noOptionsMessage,
|
|
45
|
+
onChange,
|
|
46
|
+
optionProps = {},
|
|
47
|
+
options,
|
|
48
|
+
placeholder,
|
|
49
|
+
// validationErrors,
|
|
50
|
+
value,
|
|
51
|
+
} = props;
|
|
52
|
+
|
|
53
|
+
const DropdownIndicator = (props: any) => {
|
|
54
|
+
return (
|
|
55
|
+
<components.DropdownIndicator {...props}>
|
|
56
|
+
<Icon name="fa-caret-down" ariaLabel="open dropdown" size={[14]} />
|
|
57
|
+
</components.DropdownIndicator>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const Option = (props: any) => {
|
|
62
|
+
return <components.Option {...props} {...optionProps} />;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const ComboboxComponent = () => (
|
|
66
|
+
<StyledSingleCombobox
|
|
67
|
+
aria-label={props['aria-label']}
|
|
68
|
+
aria-labelledby={props['aria-labelledby']}
|
|
69
|
+
ref={ref}
|
|
70
|
+
className={className}
|
|
71
|
+
classNamePrefix="combobox"
|
|
72
|
+
closeMenuOnSelect={closeMenuOnSelect}
|
|
73
|
+
components={{
|
|
74
|
+
DropdownIndicator: addCustomProps(DropdownIndicator, dropdownIndicatorProps),
|
|
75
|
+
Option: addCustomProps(Option, optionProps),
|
|
76
|
+
}}
|
|
77
|
+
defaultValue={defaultValue}
|
|
78
|
+
getOptionLabel={customOptionLabel}
|
|
79
|
+
getOptionValue={customOptionValue}
|
|
80
|
+
id={id}
|
|
81
|
+
isClearable={false}
|
|
82
|
+
isDisabled={disabled}
|
|
83
|
+
isMulti={false}
|
|
84
|
+
menuIsOpen={menuIsOpen}
|
|
85
|
+
noOptionsMessage={() => noOptionsMessage}
|
|
86
|
+
onChange={onChange}
|
|
87
|
+
options={options}
|
|
88
|
+
placeholder={placeholder}
|
|
89
|
+
// validationErrors={validationErrors}
|
|
90
|
+
value={value}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (props['data-cy']) {
|
|
95
|
+
return (
|
|
96
|
+
<div data-cy={props['data-cy']}>
|
|
97
|
+
<ComboboxComponent />
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return <ComboboxComponent />;
|
|
103
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SingleCombobox } from './SingleCombobox';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { css } from 'styled-components';
|
|
2
|
+
|
|
3
|
+
import { COLORS, TYPOGRAPHY } from '@/tokens';
|
|
4
|
+
|
|
5
|
+
export const basicDefaults = css`
|
|
6
|
+
font-size: ${TYPOGRAPHY.fontSize.subheadSmall};
|
|
7
|
+
color: ${COLORS.lightFontColor};
|
|
8
|
+
|
|
9
|
+
&.combobox--is-disabled {
|
|
10
|
+
pointer-events: auto;
|
|
11
|
+
opacity: 0.6;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// overall component container
|
|
15
|
+
.combobox__control {
|
|
16
|
+
box-shadow: none;
|
|
17
|
+
min-height: unset;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.combobox__control--is-disabled {
|
|
21
|
+
cursor: not-allowed;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// container component for the text input
|
|
25
|
+
.combobox__input-container {
|
|
26
|
+
color: ${COLORS.defaultFontColor};
|
|
27
|
+
cursor: text;
|
|
28
|
+
|
|
29
|
+
input {
|
|
30
|
+
margin-bottom: 0;
|
|
31
|
+
caret-color: ${COLORS.white};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// the dropdown list
|
|
36
|
+
.combobox__menu-list {
|
|
37
|
+
border-radius: inherit;
|
|
38
|
+
padding-top: 0;
|
|
39
|
+
padding-bottom: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// each available item in the dropdown list
|
|
43
|
+
.combobox__option {
|
|
44
|
+
background-color: inherit;
|
|
45
|
+
|
|
46
|
+
:hover,
|
|
47
|
+
&--is-focused {
|
|
48
|
+
background-color: ${COLORS.oxfordBlue};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// normally displays as a thin vertical line before the buttons on the far right
|
|
53
|
+
.combobox__indicator-separator {
|
|
54
|
+
display: none;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// caret button to open/close menu
|
|
58
|
+
.combobox__dropdown-indicator {
|
|
59
|
+
color: ${COLORS.lightFontColor};
|
|
60
|
+
|
|
61
|
+
:hover {
|
|
62
|
+
color: ${COLORS.white};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type ComboboxProps = {
|
|
2
|
+
'aria-label'?: string;
|
|
3
|
+
'aria-labelledby'?: string;
|
|
4
|
+
className?: string;
|
|
5
|
+
closeMenuOnSelect?: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Function to generate a custom React component for every available list `Option`
|
|
8
|
+
* @see {@link https://react-select.com/props#option}
|
|
9
|
+
*/
|
|
10
|
+
customOption?: (props: any) => JSX.Element;
|
|
11
|
+
/**
|
|
12
|
+
* Customize the text to appear in selected items.
|
|
13
|
+
*
|
|
14
|
+
* @example (option) => `${option.value}`
|
|
15
|
+
*/
|
|
16
|
+
customOptionLabel?(option: any): string;
|
|
17
|
+
/**
|
|
18
|
+
* Customize the value for the Combobox to use. Defaults to `value`
|
|
19
|
+
*
|
|
20
|
+
* @example (option) => `${option.id}`
|
|
21
|
+
*/
|
|
22
|
+
customOptionValue?(option: any): string;
|
|
23
|
+
defaultValue?: any[];
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
loading?: boolean;
|
|
26
|
+
menuIsOpen?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Custom text to show if there are no more items available to select
|
|
29
|
+
*/
|
|
30
|
+
noOptionsMessage?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Combobox defaults to expect { label: string; value: string; } at minimum - use `customOptionLabel` and `customOptionValue` props to change
|
|
33
|
+
*/
|
|
34
|
+
options: ComboboxOption[];
|
|
35
|
+
/**
|
|
36
|
+
* Input placeholder text
|
|
37
|
+
*/
|
|
38
|
+
placeholder?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// ComboboxOption is an object that needs to contain prop label, along with any other values you need returned onChange
|
|
42
|
+
export type ComboboxOption = { label?: string; [key: string]: any };
|
|
43
|
+
|
|
44
|
+
export type ComboboxSize = 'small' | 'medium';
|
|
45
|
+
export type ComboboxVariant = 'default' | 'light';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @filename useCombobox.ts
|
|
5
|
+
* Allows access to the internals of a MultiCombobox component. For example, a discrete
|
|
6
|
+
* button to clear all selected values.
|
|
7
|
+
*
|
|
8
|
+
* In your component’s file, import this hook and create a `ref` to the component.
|
|
9
|
+
* Then, pass the ref into this hook.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const comboboxEl = useRef(null);
|
|
13
|
+
* const { clearValue } = useCombobox(comboboxEl);
|
|
14
|
+
*
|
|
15
|
+
* <MultiCombobox ref={comboboxEl} ... />
|
|
16
|
+
*
|
|
17
|
+
* const handleSubmit = () => {
|
|
18
|
+
* clearValue();
|
|
19
|
+
* };
|
|
20
|
+
*
|
|
21
|
+
* @see {@link https://react-select.com/props}
|
|
22
|
+
* @see {@link https://github.com/JedWatson/react-select/discussions/4352}
|
|
23
|
+
*/
|
|
24
|
+
export function useCombobox(comboboxEl: React.RefObject<any>) {
|
|
25
|
+
const clearValue = () => {
|
|
26
|
+
if (comboboxEl?.current) {
|
|
27
|
+
comboboxEl.current.clearValue();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return { clearValue };
|
|
32
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Adds custom props to a react-select component
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* type Props = ComboboxProps & {
|
|
8
|
+
* multiValueProps: {
|
|
9
|
+
* 'data-cy': string;
|
|
10
|
+
* };
|
|
11
|
+
*
|
|
12
|
+
* <StyledMultiCombobox
|
|
13
|
+
* ...
|
|
14
|
+
* components={{
|
|
15
|
+
* MultiValue: addCustomProps(MultiValue, { ...multiValueProps }),
|
|
16
|
+
* ...
|
|
17
|
+
*
|
|
18
|
+
* @see {@link https://gist.github.com/MikaelCarpenter/1fe226967b00eea82e6a4760b244ada1}
|
|
19
|
+
*/
|
|
20
|
+
export const addCustomProps =
|
|
21
|
+
(Component: any, customProps: { [key: string]: string }) => (props: any) => {
|
|
22
|
+
return (
|
|
23
|
+
<Component {...props} innerProps={Object.assign({}, props.innerProps, { ...customProps })} />
|
|
24
|
+
);
|
|
25
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './buttons';
|
|
2
|
+
export * from './dropdowns';
|
|
3
|
+
export * from './inputs';
|
|
4
|
+
export * from './Icon';
|
|
5
|
+
export * from './loading-indicators';
|
|
6
|
+
export * from './basic-section';
|
|
7
|
+
export * from './user-feedback';
|
|
8
|
+
export * from './Accordion';
|
|
9
|
+
export * from './Pagination';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// TODO: This component's migration was fast-tracked for Insights. Assess for potential refactor and documentation.
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
5
|
+
|
|
6
|
+
import { EditableInput } from './EditableInput';
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
component: EditableInput,
|
|
10
|
+
title: 'inputs/EditableInput',
|
|
11
|
+
args: {
|
|
12
|
+
children: <h2>Input-Value</h2>,
|
|
13
|
+
},
|
|
14
|
+
argTypes: {},
|
|
15
|
+
} satisfies Meta<typeof EditableInput>;
|
|
16
|
+
|
|
17
|
+
export default meta;
|
|
18
|
+
type Story = StoryObj<typeof EditableInput>;
|
|
19
|
+
|
|
20
|
+
export const Normal: Story = {};
|
|
21
|
+
|
|
22
|
+
export const MinLengthEight: Story = {
|
|
23
|
+
args: {
|
|
24
|
+
minLength: 8,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
const transitionDuration = '200ms';
|
|
4
|
+
|
|
5
|
+
export const StyledEditableInput = styled.span`
|
|
6
|
+
&:hover .pencil-icon {
|
|
7
|
+
opacity: 1;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
[role='textbox'] > * {
|
|
11
|
+
display: inline-block;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.pencil-icon {
|
|
15
|
+
margin-left: 0.25em;
|
|
16
|
+
margin-right: 3px;
|
|
17
|
+
opacity: 0;
|
|
18
|
+
|
|
19
|
+
transition: opacity ${transitionDuration};
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// TODO: This component's migration was fast-tracked for Insights. Assess for potential refactor and documentation.
|
|
2
|
+
|
|
3
|
+
import React, { MutableRefObject, useEffect, useRef, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
// todo: how to handle/inform user when length is too short?
|
|
6
|
+
|
|
7
|
+
import classNames from 'classnames';
|
|
8
|
+
|
|
9
|
+
import { stringUtils } from '@indico-data/utils';
|
|
10
|
+
|
|
11
|
+
import { Icon } from '@/components';
|
|
12
|
+
import { PermafrostComponent } from '@/types';
|
|
13
|
+
|
|
14
|
+
import { StyledEditableInput } from './EditableInput.styles';
|
|
15
|
+
|
|
16
|
+
type Props = PermafrostComponent & {
|
|
17
|
+
children: React.ReactNode | string;
|
|
18
|
+
minLength?: number;
|
|
19
|
+
onUpdate?(newValue: string): void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Wrapper component which enables text editing in place directly on a child component,
|
|
24
|
+
* element, or plain text.
|
|
25
|
+
*/
|
|
26
|
+
export function EditableInput(props: Props) {
|
|
27
|
+
const { children, className, minLength = 3, onUpdate } = props;
|
|
28
|
+
|
|
29
|
+
const [initialValue, setInitialValue] = useState('');
|
|
30
|
+
|
|
31
|
+
// used as unique css class name
|
|
32
|
+
const { current: id } = useRef(stringUtils.createRandomString());
|
|
33
|
+
|
|
34
|
+
const editableEl = useRef() as MutableRefObject<HTMLSpanElement>;
|
|
35
|
+
|
|
36
|
+
const handleBlur = () => {
|
|
37
|
+
const node = editableEl.current.children[0] as HTMLElement;
|
|
38
|
+
const currentValue = node.innerText || editableEl.current.innerText;
|
|
39
|
+
|
|
40
|
+
if (minLength && currentValue.length < minLength) {
|
|
41
|
+
// revert to last good value
|
|
42
|
+
|
|
43
|
+
if (node) {
|
|
44
|
+
node.textContent = initialValue;
|
|
45
|
+
} else {
|
|
46
|
+
editableEl.current.textContent = initialValue;
|
|
47
|
+
}
|
|
48
|
+
} else if (currentValue !== initialValue) {
|
|
49
|
+
// allow updated value
|
|
50
|
+
setInitialValue(currentValue);
|
|
51
|
+
|
|
52
|
+
if (onUpdate) onUpdate(currentValue);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const handleKeypress = (event: React.KeyboardEvent) => {
|
|
57
|
+
if (event.key === 'Enter') {
|
|
58
|
+
editableEl.current.blur();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Records the initial value for the text, in order to check length and compare to current text.
|
|
63
|
+
// Also ensures the size of the icon is relative to the text inside the editable element (which
|
|
64
|
+
// is the icon’s *sibling*, not ancestor).
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const style = document.createElement('style');
|
|
67
|
+
const node = editableEl.current.children[0] as HTMLElement;
|
|
68
|
+
document.head.appendChild(style);
|
|
69
|
+
|
|
70
|
+
if (node) {
|
|
71
|
+
const { fontSize } = getComputedStyle(node);
|
|
72
|
+
|
|
73
|
+
style?.sheet?.insertRule(`.${id} .pencil-icon { font-size: calc(${fontSize} * 0.8) }`);
|
|
74
|
+
|
|
75
|
+
setInitialValue(node.innerText);
|
|
76
|
+
} else {
|
|
77
|
+
style?.sheet?.insertRule(`.${id} .pencil-icon { font-size: 0.8em }`);
|
|
78
|
+
setInitialValue(editableEl.current.innerText);
|
|
79
|
+
}
|
|
80
|
+
}, []);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<StyledEditableInput
|
|
84
|
+
className={classNames('EditableInput-container', id, className)}
|
|
85
|
+
data-cy={props['data-cy']}
|
|
86
|
+
id={props.id}
|
|
87
|
+
>
|
|
88
|
+
<span
|
|
89
|
+
role="textbox"
|
|
90
|
+
tabIndex={0}
|
|
91
|
+
contentEditable
|
|
92
|
+
suppressContentEditableWarning={true}
|
|
93
|
+
ref={editableEl}
|
|
94
|
+
onKeyDown={handleKeypress}
|
|
95
|
+
onBlur={handleBlur}
|
|
96
|
+
>
|
|
97
|
+
{children}
|
|
98
|
+
</span>
|
|
99
|
+
|
|
100
|
+
<Icon name="fa-pencil-alt" className="pencil-icon" />
|
|
101
|
+
</StyledEditableInput>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { EditableInput } from './EditableInput';
|