@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.
Files changed (161) hide show
  1. package/.babelrc +27 -0
  2. package/.eslintignore +6 -0
  3. package/.eslintrc.js +63 -0
  4. package/.husky/pre-commit +4 -0
  5. package/.prettierignore +3 -0
  6. package/.prettierrc +6 -0
  7. package/.stackblitzrc +4 -0
  8. package/.storybook/indico-data-logo.svg +1 -0
  9. package/.storybook/main.ts +36 -0
  10. package/.storybook/preview-head.html +19 -0
  11. package/.storybook/preview.ts +24 -0
  12. package/.storybook/themes.js +24 -0
  13. package/.yarn/releases/yarn-classic.cjs +179386 -0
  14. package/.yarnrc.yml +1 -0
  15. package/README.md +30 -0
  16. package/package.json +79 -0
  17. package/src/components/Accordion/Accordion.stories.tsx +47 -0
  18. package/src/components/Accordion/Accordion.styles.ts +35 -0
  19. package/src/components/Accordion/Accordion.tsx +30 -0
  20. package/src/components/Accordion/index.ts +1 -0
  21. package/src/components/Icon/Icon.stories.tsx +60 -0
  22. package/src/components/Icon/Icon.tsx +75 -0
  23. package/src/components/Icon/faIcons.tsx +168 -0
  24. package/src/components/Icon/index.ts +2 -0
  25. package/src/components/Icon/indicons.tsx +699 -0
  26. package/src/components/Icon/storyHelpers.tsx +87 -0
  27. package/src/components/ListTable/Header/Header.styles.ts +62 -0
  28. package/src/components/ListTable/Header/Header.tsx +67 -0
  29. package/src/components/ListTable/Header/index.ts +1 -0
  30. package/src/components/ListTable/ListTable.stories.tsx +301 -0
  31. package/src/components/ListTable/ListTable.styles.ts +76 -0
  32. package/src/components/ListTable/ListTable.tsx +135 -0
  33. package/src/components/ListTable/index.ts +1 -0
  34. package/src/components/ListTable/mock-data/index.ts +1 -0
  35. package/src/components/ListTable/mock-data/mock-data.ts +291 -0
  36. package/src/components/Pagination/Pagination.stories.tsx +45 -0
  37. package/src/components/Pagination/Pagination.styles.ts +51 -0
  38. package/src/components/Pagination/Pagination.tsx +118 -0
  39. package/src/components/Pagination/index.ts +1 -0
  40. package/src/components/basic-section/Section/Section.stories.tsx +14 -0
  41. package/src/components/basic-section/Section/Section.styles.ts +8 -0
  42. package/src/components/basic-section/Section/Section.tsx +30 -0
  43. package/src/components/basic-section/Section/index.ts +1 -0
  44. package/src/components/basic-section/SectionBlock/SectionBlock.styles.ts +15 -0
  45. package/src/components/basic-section/SectionBlock/SectionBlock.tsx +37 -0
  46. package/src/components/basic-section/SectionBlock/index.ts +1 -0
  47. package/src/components/basic-section/SectionBody/SectionBody.stories.tsx +16 -0
  48. package/src/components/basic-section/SectionBody/SectionBody.styles.ts +18 -0
  49. package/src/components/basic-section/SectionBody/SectionBody.tsx +30 -0
  50. package/src/components/basic-section/SectionBody/index.ts +1 -0
  51. package/src/components/basic-section/SectionHeader/SectionHeader.stories.tsx +17 -0
  52. package/src/components/basic-section/SectionHeader/SectionHeader.styles.ts +5 -0
  53. package/src/components/basic-section/SectionHeader/SectionHeader.tsx +35 -0
  54. package/src/components/basic-section/SectionHeader/index.ts +1 -0
  55. package/src/components/basic-section/SectionTable/SectionTable.styles.ts +237 -0
  56. package/src/components/basic-section/SectionTable/SectionTable.tsx +229 -0
  57. package/src/components/basic-section/SectionTable/index.ts +1 -0
  58. package/src/components/basic-section/index.ts +5 -0
  59. package/src/components/buttons/Button/Button.stories.tsx +80 -0
  60. package/src/components/buttons/Button/Button.styles.ts +99 -0
  61. package/src/components/buttons/Button/Button.tsx +74 -0
  62. package/src/components/buttons/Button/index.ts +1 -0
  63. package/src/components/buttons/IconButton/IconButton.stories.tsx +96 -0
  64. package/src/components/buttons/IconButton/IconButton.styles.ts +78 -0
  65. package/src/components/buttons/IconButton/IconButton.tsx +109 -0
  66. package/src/components/buttons/IconButton/index.ts +1 -0
  67. package/src/components/buttons/commonStyles.ts +108 -0
  68. package/src/components/buttons/index.ts +2 -0
  69. package/src/components/buttons/types.ts +2 -0
  70. package/src/components/dropdowns/BorderSelect/BorderSelect.stories.tsx +22 -0
  71. package/src/components/dropdowns/BorderSelect/BorderSelect.styles.ts +73 -0
  72. package/src/components/dropdowns/BorderSelect/BorderSelect.tsx +85 -0
  73. package/src/components/dropdowns/BorderSelect/index.ts +1 -0
  74. package/src/components/dropdowns/MultiCombobox/MultiCombobox.stories.tsx +146 -0
  75. package/src/components/dropdowns/MultiCombobox/MultiCombobox.styles.ts +89 -0
  76. package/src/components/dropdowns/MultiCombobox/MultiCombobox.tsx +123 -0
  77. package/src/components/dropdowns/MultiCombobox/index.ts +1 -0
  78. package/src/components/dropdowns/Select/Select.stories.tsx +54 -0
  79. package/src/components/dropdowns/Select/Select.styles.ts +73 -0
  80. package/src/components/dropdowns/Select/Select.tsx +69 -0
  81. package/src/components/dropdowns/Select/index.ts +1 -0
  82. package/src/components/dropdowns/SingleCombobox/SingleCombobox.stories.tsx +61 -0
  83. package/src/components/dropdowns/SingleCombobox/SingleCombobox.styles.ts +56 -0
  84. package/src/components/dropdowns/SingleCombobox/SingleCombobox.tsx +103 -0
  85. package/src/components/dropdowns/SingleCombobox/index.ts +1 -0
  86. package/src/components/dropdowns/commonStyles.ts +65 -0
  87. package/src/components/dropdowns/index.ts +4 -0
  88. package/src/components/dropdowns/types.ts +45 -0
  89. package/src/components/dropdowns/useCombobox.ts +32 -0
  90. package/src/components/dropdowns/utils.tsx +25 -0
  91. package/src/components/index.ts +9 -0
  92. package/src/components/inputs/EditableInput/EditableInput.stories.tsx +26 -0
  93. package/src/components/inputs/EditableInput/EditableInput.styles.ts +21 -0
  94. package/src/components/inputs/EditableInput/EditableInput.tsx +103 -0
  95. package/src/components/inputs/EditableInput/index.ts +1 -0
  96. package/src/components/inputs/NumberInput/NumberInput.stories.tsx +72 -0
  97. package/src/components/inputs/NumberInput/NumberInput.styles.ts +66 -0
  98. package/src/components/inputs/NumberInput/NumberInput.tsx +153 -0
  99. package/src/components/inputs/NumberInput/index.ts +1 -0
  100. package/src/components/inputs/SearchInput/SearchInput.stories.tsx +17 -0
  101. package/src/components/inputs/SearchInput/SearchInput.styles.ts +25 -0
  102. package/src/components/inputs/SearchInput/SearchInput.tsx +47 -0
  103. package/src/components/inputs/SearchInput/index.ts +1 -0
  104. package/src/components/inputs/TextInput/TextInput.stories.tsx +104 -0
  105. package/src/components/inputs/TextInput/TextInput.styles.ts +74 -0
  106. package/src/components/inputs/TextInput/TextInput.tsx +116 -0
  107. package/src/components/inputs/TextInput/index.ts +1 -0
  108. package/src/components/inputs/index.ts +4 -0
  109. package/src/components/inputs/inputsCommon.styles.ts +61 -0
  110. package/src/components/loading-indicators/BarSpinner/BarSpinner.stories.tsx +14 -0
  111. package/src/components/loading-indicators/BarSpinner/BarSpinner.styles.ts +53 -0
  112. package/src/components/loading-indicators/BarSpinner/BarSpinner.tsx +21 -0
  113. package/src/components/loading-indicators/BarSpinner/index.ts +1 -0
  114. package/src/components/loading-indicators/CirclePulse/CirclePulse.stories.tsx +22 -0
  115. package/src/components/loading-indicators/CirclePulse/CirclePulse.styles.ts +81 -0
  116. package/src/components/loading-indicators/CirclePulse/CirclePulse.tsx +61 -0
  117. package/src/components/loading-indicators/CirclePulse/index.ts +1 -0
  118. package/src/components/loading-indicators/CircleSpinner/CircleSpinner.stories.tsx +16 -0
  119. package/src/components/loading-indicators/CircleSpinner/CircleSpinner.tsx +37 -0
  120. package/src/components/loading-indicators/CircleSpinner/index.ts +1 -0
  121. package/src/components/loading-indicators/LoadingList/LoadingList.stories.tsx +14 -0
  122. package/src/components/loading-indicators/LoadingList/LoadingList.styles.ts +42 -0
  123. package/src/components/loading-indicators/LoadingList/LoadingList.tsx +9 -0
  124. package/src/components/loading-indicators/LoadingList/index.ts +1 -0
  125. package/src/components/loading-indicators/PercentageRing/PercentageRing.stories.tsx +18 -0
  126. package/src/components/loading-indicators/PercentageRing/PercentageRing.styles.ts +27 -0
  127. package/src/components/loading-indicators/PercentageRing/PercentageRing.tsx +76 -0
  128. package/src/components/loading-indicators/PercentageRing/index.ts +1 -0
  129. package/src/components/loading-indicators/RandomLoadingMessage/RandomLoadingMessage.stories.tsx +16 -0
  130. package/src/components/loading-indicators/RandomLoadingMessage/RandomLoadingMessage.tsx +18 -0
  131. package/src/components/loading-indicators/RandomLoadingMessage/index.ts +1 -0
  132. package/src/components/loading-indicators/RandomLoadingMessage/random-messages.js +67 -0
  133. package/src/components/loading-indicators/index.ts +6 -0
  134. package/src/components/user-feedback/Shrug/Shrug.stories.tsx +38 -0
  135. package/src/components/user-feedback/Shrug/Shrug.styles.ts +23 -0
  136. package/src/components/user-feedback/Shrug/Shrug.tsx +44 -0
  137. package/src/components/user-feedback/Shrug/index.ts +1 -0
  138. package/src/components/user-feedback/index.ts +1 -0
  139. package/src/index.tsx +18 -0
  140. package/src/styles/globals/buttons.ts +154 -0
  141. package/src/styles/globals/forms.ts +103 -0
  142. package/src/styles/globals/index.tsx +25 -0
  143. package/src/styles/globals/layout.ts +25 -0
  144. package/src/styles/globals/lists.ts +23 -0
  145. package/src/styles/globals/margin-padding.ts +33 -0
  146. package/src/styles/globals/media.ts +13 -0
  147. package/src/styles/globals/tables.ts +34 -0
  148. package/src/styles/globals/typography.ts +95 -0
  149. package/src/styles/globals/utility-classes.ts +76 -0
  150. package/src/tokens/animation.ts +6 -0
  151. package/src/tokens/breakpoints.ts +11 -0
  152. package/src/tokens/colors.ts +279 -0
  153. package/src/tokens/index.ts +20 -0
  154. package/src/tokens/margin.ts +5 -0
  155. package/src/tokens/numbers.js +41 -0
  156. package/src/tokens/padding.ts +5 -0
  157. package/src/tokens/spacings.ts +5 -0
  158. package/src/tokens/typography.ts +37 -0
  159. package/src/types.ts +6 -0
  160. package/tsconfig.json +13 -0
  161. 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,4 @@
1
+ export { BorderSelect } from './BorderSelect';
2
+ export { Select } from './Select';
3
+ export { MultiCombobox } from './MultiCombobox';
4
+ export { SingleCombobox } from './SingleCombobox';
@@ -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';