@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,72 @@
|
|
|
1
|
+
// TODO: This component's migration was fast-tracked for Insights. Assess for potential refactor and documentation.
|
|
2
|
+
|
|
3
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
|
|
5
|
+
import { NumberInput } from './NumberInput';
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
component: NumberInput,
|
|
9
|
+
title: 'inputs/NumberInput',
|
|
10
|
+
argTypes: {
|
|
11
|
+
className: { control: { disable: true } },
|
|
12
|
+
value: { control: { disable: true } },
|
|
13
|
+
},
|
|
14
|
+
} satisfies Meta<typeof NumberInput>;
|
|
15
|
+
|
|
16
|
+
export default meta;
|
|
17
|
+
type Story = StoryObj<typeof NumberInput>;
|
|
18
|
+
|
|
19
|
+
export const Base: Story = {
|
|
20
|
+
args: { label: 'Default Number Field' },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const Required: Story = {
|
|
24
|
+
args: {
|
|
25
|
+
label: 'Required Number Field',
|
|
26
|
+
required: true,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const Disabled: Story = {
|
|
31
|
+
args: {
|
|
32
|
+
label: 'Disabled Number Field',
|
|
33
|
+
disabled: true,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const WithMinimum: Story = {
|
|
38
|
+
args: {
|
|
39
|
+
label: 'Positive Number Field',
|
|
40
|
+
min: 0,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const WithMaximum: Story = {
|
|
45
|
+
args: {
|
|
46
|
+
label: 'Under 10 Number Field',
|
|
47
|
+
max: 9,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const WithMinMax: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
label: 'Select a number between 15 and 20',
|
|
54
|
+
min: 15,
|
|
55
|
+
max: 20,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const WithDecimalStep: Story = {
|
|
60
|
+
args: {
|
|
61
|
+
label: 'Enter a number',
|
|
62
|
+
step: '0.1',
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const WithPlaceholderText: Story = {
|
|
67
|
+
args: {
|
|
68
|
+
label: 'Minimum Price',
|
|
69
|
+
step: '0.1',
|
|
70
|
+
placeholder: 'none',
|
|
71
|
+
},
|
|
72
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
import { COLORS } from '@/tokens';
|
|
4
|
+
|
|
5
|
+
import { inputCommon, labelCommon } from '../inputsCommon.styles';
|
|
6
|
+
|
|
7
|
+
export const StyledNumberField = styled.div`
|
|
8
|
+
position: relative;
|
|
9
|
+
|
|
10
|
+
min-height: 40px;
|
|
11
|
+
|
|
12
|
+
&:not(.hiddenLabel) {
|
|
13
|
+
margin-top: 35px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
input {
|
|
17
|
+
${inputCommon};
|
|
18
|
+
|
|
19
|
+
padding: 5px 0 10px;
|
|
20
|
+
margin-top: 0;
|
|
21
|
+
|
|
22
|
+
-moz-appearance: textfield;
|
|
23
|
+
|
|
24
|
+
&::-webkit-inner-spin-button,
|
|
25
|
+
&::-webkit-outer-spin-button {
|
|
26
|
+
appearance: none;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&::placeholder {
|
|
30
|
+
opacity: 0.5;
|
|
31
|
+
color: ${COLORS.lightFontColor};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&:disabled {
|
|
35
|
+
background-color: transparent;
|
|
36
|
+
opacity: 0.7;
|
|
37
|
+
|
|
38
|
+
pointer-events: none;
|
|
39
|
+
|
|
40
|
+
~ .spin-buttons {
|
|
41
|
+
opacity: 0.7;
|
|
42
|
+
|
|
43
|
+
pointer-events: none;
|
|
44
|
+
cursor: not-allowed;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
label {
|
|
50
|
+
${labelCommon};
|
|
51
|
+
|
|
52
|
+
position: absolute;
|
|
53
|
+
top: -24px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.spin-buttons {
|
|
57
|
+
position: absolute;
|
|
58
|
+
right: 0;
|
|
59
|
+
bottom: 0;
|
|
60
|
+
|
|
61
|
+
display: flex;
|
|
62
|
+
flex-direction: column;
|
|
63
|
+
|
|
64
|
+
margin-bottom: 0.5em;
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// TODO: This component's migration was fast-tracked for Insights. Assess for potential refactor and documentation.
|
|
2
|
+
|
|
3
|
+
import React, { ChangeEvent, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import classNames from 'classnames';
|
|
5
|
+
import { v4 as uuid } from 'uuid';
|
|
6
|
+
|
|
7
|
+
import { Button, Icon } from '@/components';
|
|
8
|
+
import { PermafrostComponent } from '@/types';
|
|
9
|
+
|
|
10
|
+
import { StyledNumberField } from './NumberInput.styles';
|
|
11
|
+
|
|
12
|
+
export type Props = PermafrostComponent & {
|
|
13
|
+
autoFocus?: boolean;
|
|
14
|
+
decrement?(): void;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
hiddenLabel?: boolean;
|
|
17
|
+
increment?(): void;
|
|
18
|
+
label: string;
|
|
19
|
+
max?: number;
|
|
20
|
+
min?: number;
|
|
21
|
+
onChange(value: number | ''): void;
|
|
22
|
+
placeholder?: string;
|
|
23
|
+
required?: boolean;
|
|
24
|
+
step?: string;
|
|
25
|
+
tabIndex?: number;
|
|
26
|
+
value: number | '';
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Basic numeric input field. A label is required, but may be visually hidden
|
|
31
|
+
* using the `hiddenLabel` property.
|
|
32
|
+
*/
|
|
33
|
+
export function NumberInput(props: Props): React.ReactElement {
|
|
34
|
+
// ensures unique value to associate label with input
|
|
35
|
+
const fieldId = uuid();
|
|
36
|
+
const {
|
|
37
|
+
autoFocus,
|
|
38
|
+
className,
|
|
39
|
+
disabled,
|
|
40
|
+
hiddenLabel,
|
|
41
|
+
id,
|
|
42
|
+
label,
|
|
43
|
+
max,
|
|
44
|
+
min,
|
|
45
|
+
onChange,
|
|
46
|
+
placeholder,
|
|
47
|
+
required,
|
|
48
|
+
step,
|
|
49
|
+
tabIndex,
|
|
50
|
+
value,
|
|
51
|
+
} = props;
|
|
52
|
+
|
|
53
|
+
const [currentValue, setCurrentValue] = useState<number | ''>(
|
|
54
|
+
value ? value : value === 0 ? 0 : '',
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const inputEl = useRef<HTMLInputElement>(null);
|
|
58
|
+
|
|
59
|
+
const increment = () => {
|
|
60
|
+
const input = inputEl.current;
|
|
61
|
+
if (input) {
|
|
62
|
+
input.stepUp();
|
|
63
|
+
// convert string value to number
|
|
64
|
+
setCurrentValue(+input.value);
|
|
65
|
+
}
|
|
66
|
+
if (props.increment) props.increment();
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const decrement = () => {
|
|
70
|
+
const input = inputEl.current;
|
|
71
|
+
if (input) {
|
|
72
|
+
input.stepDown();
|
|
73
|
+
// convert string value to number
|
|
74
|
+
setCurrentValue(+input.value);
|
|
75
|
+
}
|
|
76
|
+
if (props.decrement) props.decrement();
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
80
|
+
const targetValue = event?.target?.value;
|
|
81
|
+
setCurrentValue(targetValue === '' ? '' : +targetValue);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// When external state changes, we update local state to match, if not already
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (currentValue === value) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
setCurrentValue(value);
|
|
90
|
+
}, [value]);
|
|
91
|
+
|
|
92
|
+
// todo: this is problematic because it means on mount the onChange handler will always be fired
|
|
93
|
+
// therefore - if the onChange is some heavy operation or call to the BE - that will automatically get called
|
|
94
|
+
// without the user even doing anything
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
// We must debounce the min/max evaluation until after the user has finished typing
|
|
97
|
+
const delayDebounceFn = setTimeout(() => {
|
|
98
|
+
if ((min || min === 0) && +currentValue < min) {
|
|
99
|
+
setCurrentValue('');
|
|
100
|
+
onChange('');
|
|
101
|
+
} else if ((max || max === 0) && +currentValue > max) {
|
|
102
|
+
setCurrentValue('');
|
|
103
|
+
onChange('');
|
|
104
|
+
} else if (+currentValue < 0) {
|
|
105
|
+
setCurrentValue('');
|
|
106
|
+
onChange('');
|
|
107
|
+
} else {
|
|
108
|
+
onChange(currentValue);
|
|
109
|
+
}
|
|
110
|
+
}, 1000);
|
|
111
|
+
return () => clearTimeout(delayDebounceFn);
|
|
112
|
+
}, [currentValue]);
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<StyledNumberField
|
|
116
|
+
className={classNames(className, {
|
|
117
|
+
hiddenLabel,
|
|
118
|
+
})}
|
|
119
|
+
data-cy={props['data-cy']}
|
|
120
|
+
id={id}
|
|
121
|
+
>
|
|
122
|
+
<input
|
|
123
|
+
tabIndex={tabIndex}
|
|
124
|
+
autoFocus={autoFocus}
|
|
125
|
+
id={fieldId}
|
|
126
|
+
type="number"
|
|
127
|
+
value={currentValue}
|
|
128
|
+
disabled={disabled}
|
|
129
|
+
max={max}
|
|
130
|
+
min={min}
|
|
131
|
+
onChange={handleChange}
|
|
132
|
+
placeholder={placeholder}
|
|
133
|
+
required={required}
|
|
134
|
+
step={step}
|
|
135
|
+
aria-label={hiddenLabel ? label : ''}
|
|
136
|
+
ref={inputEl}
|
|
137
|
+
/>
|
|
138
|
+
|
|
139
|
+
{!hiddenLabel && <label htmlFor={fieldId}>{label}</label>}
|
|
140
|
+
|
|
141
|
+
{/* since user can increment/decrement using arrow keys, these don’t need to be tabbable */}
|
|
142
|
+
<div className="spin-buttons">
|
|
143
|
+
<Button variant="no-style" onClick={increment} tabindex={-1}>
|
|
144
|
+
<Icon name="fa-caret-up" ariaLabel="increase" />
|
|
145
|
+
</Button>
|
|
146
|
+
|
|
147
|
+
<Button variant="no-style" onClick={decrement} tabindex={-1}>
|
|
148
|
+
<Icon name="fa-caret-down" ariaLabel="decrease" />
|
|
149
|
+
</Button>
|
|
150
|
+
</div>
|
|
151
|
+
</StyledNumberField>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { NumberInput } from './NumberInput';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// TODO: This component's migration was fast-tracked for Insights. Assess for potential refactor and documentation.
|
|
2
|
+
|
|
3
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
|
|
5
|
+
import { SearchInput } from './SearchInput';
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
component: SearchInput,
|
|
9
|
+
title: 'inputs/SearchInput',
|
|
10
|
+
argTypes: {},
|
|
11
|
+
args: { placeholder: 'Search field placeholder' },
|
|
12
|
+
} satisfies Meta<typeof SearchInput>;
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
type Story = StoryObj<typeof SearchInput>;
|
|
16
|
+
|
|
17
|
+
export const Base: Story = {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
import { TYPOGRAPHY } from '@/tokens';
|
|
4
|
+
|
|
5
|
+
import { inputCommon } from '../inputsCommon.styles';
|
|
6
|
+
|
|
7
|
+
export const StyledSearchField = styled.div`
|
|
8
|
+
position: relative;
|
|
9
|
+
background: transparent;
|
|
10
|
+
|
|
11
|
+
label {
|
|
12
|
+
position: absolute;
|
|
13
|
+
top: 4px;
|
|
14
|
+
left: 0;
|
|
15
|
+
font-size: ${TYPOGRAPHY.fontSize.subheadSmall};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
input {
|
|
19
|
+
${inputCommon};
|
|
20
|
+
|
|
21
|
+
padding: 5px 5px 5px 25px;
|
|
22
|
+
font-size: ${TYPOGRAPHY.fontSize.base};
|
|
23
|
+
margin-bottom: 0;
|
|
24
|
+
}
|
|
25
|
+
`;
|
|
@@ -0,0 +1,47 @@
|
|
|
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 { v4 as uuid } from 'uuid';
|
|
5
|
+
|
|
6
|
+
import { Icon } from '@/components';
|
|
7
|
+
import { PermafrostComponent } from '@/types';
|
|
8
|
+
|
|
9
|
+
import { StyledSearchField } from './SearchInput.styles';
|
|
10
|
+
|
|
11
|
+
type Props = PermafrostComponent & {
|
|
12
|
+
inputProps?: { [key: string]: string };
|
|
13
|
+
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
14
|
+
onKeyUp?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
value?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const SearchInput = (props: Props) => {
|
|
20
|
+
const { className, inputProps, onChange, onKeyUp, placeholder, value } = props;
|
|
21
|
+
|
|
22
|
+
const id = uuid();
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<StyledSearchField className={className} data-cy={props['data-cy']} id={props.id}>
|
|
26
|
+
<label htmlFor={id}>
|
|
27
|
+
<Icon name="fa-search" ariaLabel="search" />
|
|
28
|
+
</label>
|
|
29
|
+
|
|
30
|
+
<input
|
|
31
|
+
type="search"
|
|
32
|
+
id={id}
|
|
33
|
+
placeholder={placeholder}
|
|
34
|
+
value={value}
|
|
35
|
+
onChange={onChange}
|
|
36
|
+
onKeyUp={onKeyUp}
|
|
37
|
+
{...inputProps}
|
|
38
|
+
/>
|
|
39
|
+
</StyledSearchField>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
SearchInput.defaultProps = {
|
|
44
|
+
className: '',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default SearchInput;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SearchInput } from './SearchInput';
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// TODO: This component's migration was fast-tracked for Insights. Assess for potential refactor and documentation.
|
|
2
|
+
|
|
3
|
+
import React, { ChangeEvent, useState } from 'react';
|
|
4
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
5
|
+
|
|
6
|
+
import { TextInput } from './TextInput';
|
|
7
|
+
|
|
8
|
+
const validationErrors = [
|
|
9
|
+
'Some helpful error to display to user',
|
|
10
|
+
'Another error that may not be so helpful',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function StoryRender(props) {
|
|
14
|
+
const [value, setValue] = useState<string>(props.value);
|
|
15
|
+
|
|
16
|
+
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
17
|
+
setValue(event.target.value);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return <TextInput {...props} onChange={handleChange} value={value} />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const meta = {
|
|
24
|
+
component: TextInput,
|
|
25
|
+
title: 'inputs/TextInput',
|
|
26
|
+
argTypes: {
|
|
27
|
+
autoComplete: { control: { disable: true } },
|
|
28
|
+
className: { control: { disable: true } },
|
|
29
|
+
defaultValue: { control: { disable: true } },
|
|
30
|
+
inputProps: { control: { disable: true } },
|
|
31
|
+
name: { control: { disable: true } },
|
|
32
|
+
promptProps: { control: { disable: true } },
|
|
33
|
+
type: { control: { disable: true } },
|
|
34
|
+
validationErrors: { control: { disable: true } },
|
|
35
|
+
value: { control: { disable: true } },
|
|
36
|
+
},
|
|
37
|
+
args: {},
|
|
38
|
+
} satisfies Meta<typeof TextInput>;
|
|
39
|
+
|
|
40
|
+
export default meta;
|
|
41
|
+
type Story = StoryObj<typeof TextInput>;
|
|
42
|
+
|
|
43
|
+
export const Base: Story = {
|
|
44
|
+
args: { label: 'Default Text Field', placeholder: 'Placeholder for Default Input' },
|
|
45
|
+
render: (args) => {
|
|
46
|
+
return <StoryRender {...args} />;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const Required: Story = {
|
|
51
|
+
args: { label: 'Required Text Field', placeholder: 'Required Placeholder', required: true },
|
|
52
|
+
render: (args) => {
|
|
53
|
+
return <StoryRender {...args} />;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const WithPrompt: Story = {
|
|
58
|
+
args: {
|
|
59
|
+
label: 'Text Field with Prompts',
|
|
60
|
+
placeholder: 'Prompt Example',
|
|
61
|
+
displayErrors: false,
|
|
62
|
+
required: true,
|
|
63
|
+
validationErrors,
|
|
64
|
+
},
|
|
65
|
+
render: (args) => {
|
|
66
|
+
return <StoryRender {...args} />;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const WithError: Story = {
|
|
71
|
+
args: {
|
|
72
|
+
label: 'Text Field with Errors',
|
|
73
|
+
placeholder: 'Error Example',
|
|
74
|
+
displayErrors: true,
|
|
75
|
+
validationErrors,
|
|
76
|
+
value: 'This text is incorrect for some reason',
|
|
77
|
+
},
|
|
78
|
+
render: (args) => {
|
|
79
|
+
return <StoryRender {...args} />;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const Disabled: Story = {
|
|
84
|
+
args: {
|
|
85
|
+
label: 'Disabled Text Field',
|
|
86
|
+
placeholder: 'Disabled field',
|
|
87
|
+
displayErrors: true,
|
|
88
|
+
disabled: true,
|
|
89
|
+
},
|
|
90
|
+
render: (args) => {
|
|
91
|
+
return <StoryRender {...args} />;
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const CharacterLimit: Story = {
|
|
96
|
+
args: {
|
|
97
|
+
label: 'Text Field with a character limit',
|
|
98
|
+
maxLength: 12,
|
|
99
|
+
value: '',
|
|
100
|
+
},
|
|
101
|
+
render: (args) => {
|
|
102
|
+
return <StoryRender {...args} />;
|
|
103
|
+
},
|
|
104
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
import { ANIMATION, COLORS, TYPOGRAPHY } from '@/tokens';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
inputInfoCommon,
|
|
7
|
+
inputCommon,
|
|
8
|
+
labelCommon,
|
|
9
|
+
placeholderCommon,
|
|
10
|
+
} from '../inputsCommon.styles';
|
|
11
|
+
|
|
12
|
+
export const StyledTextInput = styled.div`
|
|
13
|
+
position: relative;
|
|
14
|
+
color: ${COLORS.defaultFontColor};
|
|
15
|
+
font-family: ${TYPOGRAPHY.fontFamily.base};
|
|
16
|
+
|
|
17
|
+
min-height: 40px;
|
|
18
|
+
margin-top: 35px;
|
|
19
|
+
|
|
20
|
+
label {
|
|
21
|
+
pointer-events: none;
|
|
22
|
+
|
|
23
|
+
position: absolute;
|
|
24
|
+
top: 6px;
|
|
25
|
+
|
|
26
|
+
transition: transform ${ANIMATION.duration} ${ANIMATION.timing};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
input {
|
|
30
|
+
${inputCommon};
|
|
31
|
+
|
|
32
|
+
padding: 5px 0 10px;
|
|
33
|
+
margin-top: 0;
|
|
34
|
+
|
|
35
|
+
border: 0 solid currentColor;
|
|
36
|
+
border-bottom-width: 1px;
|
|
37
|
+
|
|
38
|
+
&::placeholder {
|
|
39
|
+
color: inherit;
|
|
40
|
+
opacity: 0;
|
|
41
|
+
|
|
42
|
+
transition: opacity ${ANIMATION.duration} ${ANIMATION.timing};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&:focus,
|
|
46
|
+
&.filled {
|
|
47
|
+
${placeholderCommon}
|
|
48
|
+
|
|
49
|
+
+ label {
|
|
50
|
+
${labelCommon};
|
|
51
|
+
|
|
52
|
+
transform: translateY(-30px);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
export const StyledInputInfo = styled.div`
|
|
59
|
+
${inputInfoCommon};
|
|
60
|
+
|
|
61
|
+
display: flex;
|
|
62
|
+
|
|
63
|
+
p {
|
|
64
|
+
margin: 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.count {
|
|
68
|
+
margin-left: auto;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.isMax {
|
|
72
|
+
color: ${COLORS.white};
|
|
73
|
+
}
|
|
74
|
+
`;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// TODO: This component's migration was fast-tracked for Insights. Assess for potential refactor and documentation.
|
|
2
|
+
|
|
3
|
+
import React, { ChangeEvent } from 'react';
|
|
4
|
+
import classNames from 'classnames';
|
|
5
|
+
import { v4 as uuid } from 'uuid';
|
|
6
|
+
|
|
7
|
+
import { PermafrostComponent } from '@/types';
|
|
8
|
+
|
|
9
|
+
import { StyledInputInfo, StyledTextInput } from './TextInput.styles';
|
|
10
|
+
|
|
11
|
+
export type Props = PermafrostComponent & {
|
|
12
|
+
autoComplete?: 'email' | 'current-password' | 'new-password' | string;
|
|
13
|
+
autoFocus?: boolean;
|
|
14
|
+
defaultValue?: string;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
displayErrors?: boolean;
|
|
17
|
+
inputProps?: any;
|
|
18
|
+
label?: string;
|
|
19
|
+
maxLength?: number;
|
|
20
|
+
name?: string;
|
|
21
|
+
onBlur?(): void;
|
|
22
|
+
onChange?(event: ChangeEvent<HTMLInputElement>): void;
|
|
23
|
+
onFocus?(): void;
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
promptProps?: any;
|
|
26
|
+
required?: boolean;
|
|
27
|
+
type?: 'text' | 'email' | 'password' | string;
|
|
28
|
+
validationErrors?: string[];
|
|
29
|
+
value?: string;
|
|
30
|
+
readOnly?: boolean;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function TextInput(props: Props): React.ReactElement {
|
|
34
|
+
// ensures unique value to associate label with input
|
|
35
|
+
const inputId = uuid();
|
|
36
|
+
const {
|
|
37
|
+
autoComplete,
|
|
38
|
+
autoFocus,
|
|
39
|
+
className,
|
|
40
|
+
defaultValue,
|
|
41
|
+
disabled,
|
|
42
|
+
displayErrors,
|
|
43
|
+
id,
|
|
44
|
+
inputProps,
|
|
45
|
+
label,
|
|
46
|
+
maxLength,
|
|
47
|
+
name,
|
|
48
|
+
onBlur,
|
|
49
|
+
onChange,
|
|
50
|
+
onFocus,
|
|
51
|
+
placeholder,
|
|
52
|
+
promptProps,
|
|
53
|
+
required,
|
|
54
|
+
type = 'text',
|
|
55
|
+
validationErrors,
|
|
56
|
+
value,
|
|
57
|
+
readOnly,
|
|
58
|
+
} = props;
|
|
59
|
+
|
|
60
|
+
const errorClass: string = displayErrors ? 'error' : 'prompt';
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<StyledTextInput className={className} data-cy={props['data-cy']} id={id}>
|
|
64
|
+
<input
|
|
65
|
+
autoComplete={autoComplete}
|
|
66
|
+
autoFocus={autoFocus}
|
|
67
|
+
className={classNames('text-input__input-element', {
|
|
68
|
+
filled: (value && value.length > 0) || defaultValue,
|
|
69
|
+
})}
|
|
70
|
+
disabled={disabled}
|
|
71
|
+
defaultValue={defaultValue}
|
|
72
|
+
id={inputId}
|
|
73
|
+
maxLength={maxLength}
|
|
74
|
+
name={name}
|
|
75
|
+
onBlur={onBlur}
|
|
76
|
+
onChange={onChange}
|
|
77
|
+
onFocus={onFocus}
|
|
78
|
+
onKeyUp={(e) => {
|
|
79
|
+
if (e.key === 'Enter') {
|
|
80
|
+
e.currentTarget.blur();
|
|
81
|
+
}
|
|
82
|
+
}}
|
|
83
|
+
placeholder={placeholder}
|
|
84
|
+
required={required}
|
|
85
|
+
type={type}
|
|
86
|
+
value={value}
|
|
87
|
+
readOnly={readOnly}
|
|
88
|
+
{...inputProps}
|
|
89
|
+
/>
|
|
90
|
+
|
|
91
|
+
{label ? <label htmlFor={inputId}>{label}</label> : null}
|
|
92
|
+
|
|
93
|
+
{(validationErrors || maxLength) && (
|
|
94
|
+
<StyledInputInfo>
|
|
95
|
+
{validationErrors && (
|
|
96
|
+
<div>
|
|
97
|
+
{validationErrors.map((err, i) => {
|
|
98
|
+
return (
|
|
99
|
+
<p className={errorClass} key={i} {...promptProps}>
|
|
100
|
+
{err}
|
|
101
|
+
</p>
|
|
102
|
+
);
|
|
103
|
+
})}
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
|
|
107
|
+
{maxLength && (
|
|
108
|
+
<div className={classNames('count', { isMax: value?.length === maxLength })}>
|
|
109
|
+
{value?.length || 0} / {maxLength}
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
</StyledInputInfo>
|
|
113
|
+
)}
|
|
114
|
+
</StyledTextInput>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TextInput } from './TextInput';
|