@platformatic/ui-components 0.2.16 → 0.2.18
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/package.json +1 -1
- package/src/components/LoginButton.jsx +1 -1
- package/src/components/TooltipV2.jsx +0 -1
- package/src/components/forms/Select.jsx +7 -58
- package/src/components/forms/Select.module.css +5 -2
- package/src/components/forms/SelectWithInput.jsx +327 -0
- package/src/components/forms/SelectWithInput.module.css +40 -0
- package/src/components/forms/index.js +2 -0
- package/src/stories/forms/Select.stories.jsx +3 -16
- package/src/stories/forms/SelectWithInput.stories.jsx +176 -0
package/package.json
CHANGED
|
@@ -3,6 +3,6 @@ import Button from './Button'
|
|
|
3
3
|
|
|
4
4
|
export default function LoginButton ({ iconName, label, onClick, ...props }) {
|
|
5
5
|
return (
|
|
6
|
-
<Button backgroundColor='main-green' alt={label} label={label} platformaticIcon={{ iconName, size: 'medium', color: 'main-dark-blue' }} data-testid='login-button' size='extra-large' onClick={onClick} {...props}
|
|
6
|
+
<Button backgroundColor='main-green' alt={label} label={label} platformaticIcon={{ iconName, size: 'medium', color: 'main-dark-blue' }} data-testid='login-button' size='extra-large' onClick={onClick} {...props} />
|
|
7
7
|
)
|
|
8
8
|
}
|
|
@@ -13,7 +13,6 @@ function TooltipV2 ({ tooltipClassName, text, visible, alignment, elementClassNa
|
|
|
13
13
|
if (visible) {
|
|
14
14
|
setClassName(visibleClassName())
|
|
15
15
|
if (ref.current) {
|
|
16
|
-
console.log('elementClassName', elementClassName)
|
|
17
16
|
const referenceBoundingClientRect = document.getElementsByClassName(elementClassName)[0]?.getBoundingClientRect()
|
|
18
17
|
if (referenceBoundingClientRect) {
|
|
19
18
|
const topPosition = referenceBoundingClientRect.y - (referenceBoundingClientRect.height)
|
|
@@ -17,12 +17,9 @@ function Select ({
|
|
|
17
17
|
borderColor,
|
|
18
18
|
borderListColor,
|
|
19
19
|
errorMessage,
|
|
20
|
-
onChange,
|
|
21
20
|
onSelect,
|
|
22
|
-
onClear,
|
|
23
21
|
disabled,
|
|
24
22
|
mainColor,
|
|
25
|
-
optionSelected,
|
|
26
23
|
dataAttrName,
|
|
27
24
|
dataAttrValue,
|
|
28
25
|
backgroundColor,
|
|
@@ -30,6 +27,7 @@ function Select ({
|
|
|
30
27
|
}) {
|
|
31
28
|
const inputRef = useRef()
|
|
32
29
|
const [showOptions, setShowOptions] = useState(false)
|
|
30
|
+
// eslint-disable-next-line no-unused-vars
|
|
33
31
|
const [isSelected, setIsSelected] = useState(false)
|
|
34
32
|
const [isOnFocus, setIsOnFocus] = useState(false)
|
|
35
33
|
|
|
@@ -41,6 +39,7 @@ function Select ({
|
|
|
41
39
|
let optionsClassName = `${styles.options} ${defaultOptionsClassName} `
|
|
42
40
|
inputClassName += ' ' + commonStyles[`background-color-${backgroundColor}`]
|
|
43
41
|
optionsClassName += commonStyles[`background-color-${backgroundColor}`]
|
|
42
|
+
const optionSelectedClassName = styles.optionSelected
|
|
44
43
|
|
|
45
44
|
if (borderListColor) {
|
|
46
45
|
optionsClassName += ' ' + styles['bordered-options']
|
|
@@ -94,20 +93,11 @@ function Select ({
|
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
useEffect(() => {
|
|
97
|
-
if (value.length > 0 && !showOptions && !isSelected) {
|
|
98
|
-
setShowOptions(true)
|
|
99
|
-
}
|
|
100
96
|
if (value.length === 0) {
|
|
101
97
|
setIsSelected(false)
|
|
102
98
|
}
|
|
103
99
|
}, [value])
|
|
104
100
|
|
|
105
|
-
useEffect(() => {
|
|
106
|
-
if (optionSelected) {
|
|
107
|
-
setSelected(optionSelected)
|
|
108
|
-
}
|
|
109
|
-
}, [optionSelected])
|
|
110
|
-
|
|
111
101
|
useEffect(() => {
|
|
112
102
|
if (disabled) {
|
|
113
103
|
const className = normalClassName() + ' ' + commonStyles['apply-opacity-30']
|
|
@@ -117,10 +107,10 @@ function Select ({
|
|
|
117
107
|
}
|
|
118
108
|
}, [disabled])
|
|
119
109
|
|
|
120
|
-
function renderLi (option, index) {
|
|
110
|
+
function renderLi (option, index, isOptionSelected) {
|
|
121
111
|
return (
|
|
122
112
|
<li
|
|
123
|
-
key={index} className={singleOptionClassName} onClick={() => {
|
|
113
|
+
key={index} className={`${isOptionSelected ? optionSelectedClassName : singleOptionClassName}`} onClick={() => {
|
|
124
114
|
if (option.notSelectable) {
|
|
125
115
|
return handleNotSelectable(option.onClick && option.onClick())
|
|
126
116
|
}
|
|
@@ -137,34 +127,15 @@ function Select ({
|
|
|
137
127
|
}
|
|
138
128
|
|
|
139
129
|
function renderOptions () {
|
|
140
|
-
if (value.length === 0) {
|
|
141
|
-
return (
|
|
142
|
-
<ul className={optionsClassName}>
|
|
143
|
-
{options.length > 0 ? options.map((option, index) => renderLi(option, index)) : <li className={singleOptionClassName}><div className={styles.liContent}><span className={commonStyles[`text--${mainColor}`]}>No data found</span></div></li>}
|
|
144
|
-
</ul>
|
|
145
|
-
)
|
|
146
|
-
}
|
|
147
130
|
const notFilterableOptions = options.filter(option => option.notFilterable)
|
|
148
|
-
const filteredOptions = options.filter(option => !option.notFilterable).filter(option => option.label.toLowerCase().includes(value.toLowerCase()))
|
|
149
|
-
|
|
150
131
|
return (
|
|
151
132
|
<ul className={optionsClassName}>
|
|
152
|
-
{filteredOptions.length > 0 ? filteredOptions.map((option, index) => renderLi(option, index)) : <li className={singleOptionClassName}><div className={styles.liContent}><span className={commonStyles[`text--${mainColor}`]}>No data found</span></div></li>}
|
|
153
133
|
{notFilterableOptions.length > 0 && notFilterableOptions.map((option, index) => renderLi(option, index))}
|
|
134
|
+
{options.length > 0 ? options.map((option, index) => renderLi(option, index, option.label === value)) : <li className={singleOptionClassName}><div className={styles.liContent}><span className={commonStyles[`text--${mainColor}`]}>No data found</span></div></li>}
|
|
154
135
|
</ul>
|
|
155
136
|
)
|
|
156
137
|
}
|
|
157
138
|
|
|
158
|
-
// https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-change-or-input-event-in-react-js
|
|
159
|
-
function clearValue () {
|
|
160
|
-
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set
|
|
161
|
-
nativeInputValueSetter.call(inputRef.current, '')
|
|
162
|
-
const ev2 = new Event('input', { bubbles: true })
|
|
163
|
-
ev2.simulated = true
|
|
164
|
-
inputRef.current.dispatchEvent(ev2)
|
|
165
|
-
onClear()
|
|
166
|
-
}
|
|
167
|
-
|
|
168
139
|
function handleFocus () {
|
|
169
140
|
if (!isOnFocus) {
|
|
170
141
|
setIsOnFocus(true)
|
|
@@ -177,7 +148,7 @@ function Select ({
|
|
|
177
148
|
event.preventDefault()
|
|
178
149
|
setTimeout(() => {
|
|
179
150
|
if (showOptions) {
|
|
180
|
-
setShowOptions(false)
|
|
151
|
+
// setShowOptions(false)
|
|
181
152
|
setIsOnFocus(false)
|
|
182
153
|
setWrapperClassName(normalClassName())
|
|
183
154
|
}
|
|
@@ -187,9 +158,8 @@ function Select ({
|
|
|
187
158
|
return (
|
|
188
159
|
<div className={containerClassName} {...dataProps}>
|
|
189
160
|
<div className={styles.selectContainer}>
|
|
190
|
-
<input type='text' name={name} value={value} className={wrapperClassName} ref={inputRef}
|
|
161
|
+
<input type='text' name={name} value={value} className={wrapperClassName} ref={inputRef} disabled={disabled} placeholder={placeholder} onFocus={() => handleFocus()} onBlur={(e) => handleBlur(e)} readOnly />
|
|
191
162
|
<div className={styles.icons}>
|
|
192
|
-
{value?.length > 0 && !disabled && <PlatformaticIcon iconName='CircleCloseIcon' color={borderColor} onClick={() => clearValue()} size={SMALL} />}
|
|
193
163
|
<PlatformaticIcon iconName={showOptions ? 'ArrowUpIcon' : 'ArrowDownIcon'} color={borderColor} disabled={disabled} onClick={() => setShowOptions(!showOptions)} size={SMALL} />
|
|
194
164
|
</div>
|
|
195
165
|
</div>
|
|
@@ -252,18 +222,10 @@ Select.propTypes = {
|
|
|
252
222
|
* color of border UL
|
|
253
223
|
*/
|
|
254
224
|
borderListColor: PropTypes.oneOf([MAIN_GREEN, MAIN_DARK_BLUE, WHITE]),
|
|
255
|
-
/**
|
|
256
|
-
* onChange
|
|
257
|
-
*/
|
|
258
|
-
onChange: PropTypes.func,
|
|
259
225
|
/**
|
|
260
226
|
* onSelect
|
|
261
227
|
*/
|
|
262
228
|
onSelect: PropTypes.func,
|
|
263
|
-
/**
|
|
264
|
-
* onClear
|
|
265
|
-
*/
|
|
266
|
-
onClear: PropTypes.func,
|
|
267
229
|
/**
|
|
268
230
|
* Disabled
|
|
269
231
|
*/
|
|
@@ -272,16 +234,6 @@ Select.propTypes = {
|
|
|
272
234
|
* mainColor
|
|
273
235
|
*/
|
|
274
236
|
mainColor: PropTypes.oneOf([MAIN_DARK_BLUE, WHITE]),
|
|
275
|
-
/**
|
|
276
|
-
* optionSelected
|
|
277
|
-
*/
|
|
278
|
-
optionSelected: PropTypes.shape({
|
|
279
|
-
label: PropTypes.string,
|
|
280
|
-
value: PropTypes.oneOfType([
|
|
281
|
-
PropTypes.string,
|
|
282
|
-
PropTypes.number
|
|
283
|
-
])
|
|
284
|
-
}),
|
|
285
237
|
/**
|
|
286
238
|
* dataAttrName
|
|
287
239
|
*/
|
|
@@ -312,12 +264,9 @@ Select.defaultProps = {
|
|
|
312
264
|
borderColor: MAIN_GREEN,
|
|
313
265
|
borderListColor: '',
|
|
314
266
|
errorMessage: '',
|
|
315
|
-
onChange: () => {},
|
|
316
267
|
onSelect: () => {},
|
|
317
|
-
onClear: () => {},
|
|
318
268
|
disabled: false,
|
|
319
269
|
mainColor: MAIN_DARK_BLUE,
|
|
320
|
-
optionSelected: null,
|
|
321
270
|
dataAttrName: '',
|
|
322
271
|
dataAttrValue: '',
|
|
323
272
|
backgroundColor: WHITE,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
@apply outline-none;
|
|
18
18
|
}
|
|
19
19
|
.options {
|
|
20
|
-
@apply absolute left-0 top-[42px] w-
|
|
20
|
+
@apply absolute left-0 top-[42px] w-[calc(100%-1rem)] max-h-[216px] overflow-y-auto z-20 p-2;
|
|
21
21
|
}
|
|
22
22
|
.bordered-options {
|
|
23
23
|
@apply border border-solid rounded
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
.option {
|
|
26
26
|
@apply font-light cursor-pointer z-10 flex flex-row justify-between items-center px-4;
|
|
27
27
|
}
|
|
28
|
+
.optionSelected {
|
|
29
|
+
@apply font-light cursor-pointer z-10 flex flex-row justify-between items-center px-4 bg-white/15;
|
|
30
|
+
}
|
|
28
31
|
.bordered-bottom {
|
|
29
32
|
@apply border-b
|
|
30
33
|
}
|
|
@@ -33,7 +36,7 @@
|
|
|
33
36
|
transform: translateY(-50%);
|
|
34
37
|
}
|
|
35
38
|
.liContent {
|
|
36
|
-
@apply py-2 flex items-center gap-x-1 relative z-[-1] w-
|
|
39
|
+
@apply py-2 flex items-center gap-x-1 relative z-[-1] w-full;
|
|
37
40
|
}
|
|
38
41
|
.descriptionValue {
|
|
39
42
|
@apply opacity-70
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
import React, { useState, useEffect, useRef } from 'react'
|
|
3
|
+
import PropTypes from 'prop-types'
|
|
4
|
+
import styles from './SelectWithInput.module.css'
|
|
5
|
+
import commonStyles from '../Common.module.css'
|
|
6
|
+
import { MAIN_DARK_BLUE, MAIN_GREEN, RICH_BLACK, SMALL, WHITE } from '../constants'
|
|
7
|
+
import PlatformaticIcon from '../PlatformaticIcon'
|
|
8
|
+
|
|
9
|
+
function SelectWithInput ({
|
|
10
|
+
defaultContainerClassName,
|
|
11
|
+
placeholder,
|
|
12
|
+
name,
|
|
13
|
+
value,
|
|
14
|
+
options,
|
|
15
|
+
defaultOptionsClassName,
|
|
16
|
+
optionsBorderedBottom,
|
|
17
|
+
borderColor,
|
|
18
|
+
borderListColor,
|
|
19
|
+
errorMessage,
|
|
20
|
+
onChange,
|
|
21
|
+
onSelect,
|
|
22
|
+
onClear,
|
|
23
|
+
disabled,
|
|
24
|
+
mainColor,
|
|
25
|
+
optionSelected,
|
|
26
|
+
dataAttrName,
|
|
27
|
+
dataAttrValue,
|
|
28
|
+
backgroundColor,
|
|
29
|
+
inputTextClassName
|
|
30
|
+
}) {
|
|
31
|
+
const inputRef = useRef()
|
|
32
|
+
const [showOptions, setShowOptions] = useState(false)
|
|
33
|
+
const [isSelected, setIsSelected] = useState(false)
|
|
34
|
+
const [isOnFocus, setIsOnFocus] = useState(false)
|
|
35
|
+
|
|
36
|
+
const showError = errorMessage.length > 0
|
|
37
|
+
const containerClassName = `${styles.container} ${defaultContainerClassName} `
|
|
38
|
+
let inputClassName = `${commonStyles.fullWidth} ${styles.select} ${inputTextClassName}`
|
|
39
|
+
inputClassName += ' ' + styles[`select-${mainColor}`]
|
|
40
|
+
inputClassName += ' ' + commonStyles[`text--${borderColor}`]
|
|
41
|
+
let optionsClassName = `${styles.options} ${defaultOptionsClassName} `
|
|
42
|
+
inputClassName += ' ' + commonStyles[`background-color-${backgroundColor}`]
|
|
43
|
+
optionsClassName += commonStyles[`background-color-${backgroundColor}`]
|
|
44
|
+
|
|
45
|
+
if (borderListColor) {
|
|
46
|
+
optionsClassName += ' ' + styles['bordered-options']
|
|
47
|
+
optionsClassName += ' ' + commonStyles[`bordered--${borderListColor}-30`]
|
|
48
|
+
}
|
|
49
|
+
let singleOptionClassName = `${styles.option} ` + commonStyles[`bordered--${mainColor}-70`] + ' ' + commonStyles[`hover-background-color-opaque-${mainColor}`]
|
|
50
|
+
if (optionsBorderedBottom) {
|
|
51
|
+
singleOptionClassName += ` ${styles['bordered-bottom']}`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const optionSpanClassName = commonStyles[`text--${mainColor}`]
|
|
55
|
+
|
|
56
|
+
const [wrapperClassName, setWrapperClassName] = useState(normalClassName())
|
|
57
|
+
|
|
58
|
+
function onFocusClassName () {
|
|
59
|
+
return inputClassName + ' ' + commonStyles[`bordered--${borderColor}-100`]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalClassName () {
|
|
63
|
+
const baseClassName = inputClassName
|
|
64
|
+
if (showError) {
|
|
65
|
+
inputClassName += ' ' + commonStyles['bordered--error-red']
|
|
66
|
+
} else {
|
|
67
|
+
inputClassName += ' ' + commonStyles[`bordered--${borderColor}-70`]
|
|
68
|
+
}
|
|
69
|
+
if (disabled) inputClassName += ' ' + commonStyles['apply-opacity-30']
|
|
70
|
+
return baseClassName
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const dataProps = {}
|
|
74
|
+
if (dataAttrName && dataAttrValue) {
|
|
75
|
+
dataProps[`data-${dataAttrName}`] = dataAttrValue
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function handleNotSelectable (callback = () => {}) {
|
|
79
|
+
setIsSelected(true)
|
|
80
|
+
setShowOptions(false)
|
|
81
|
+
callback()
|
|
82
|
+
}
|
|
83
|
+
function setSelected ({ label, value }) {
|
|
84
|
+
setIsSelected(true)
|
|
85
|
+
setShowOptions(false)
|
|
86
|
+
/* eslint-disable-next-line no-undef */
|
|
87
|
+
onSelect(new CustomEvent('valueSelected', {
|
|
88
|
+
detail: {
|
|
89
|
+
name,
|
|
90
|
+
label,
|
|
91
|
+
value
|
|
92
|
+
}
|
|
93
|
+
}))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (value.length > 0 && !showOptions && !isSelected) {
|
|
98
|
+
setShowOptions(true)
|
|
99
|
+
}
|
|
100
|
+
if (value.length === 0) {
|
|
101
|
+
setIsSelected(false)
|
|
102
|
+
}
|
|
103
|
+
}, [value])
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (optionSelected) {
|
|
107
|
+
setSelected(optionSelected)
|
|
108
|
+
}
|
|
109
|
+
}, [optionSelected])
|
|
110
|
+
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (disabled) {
|
|
113
|
+
const className = normalClassName() + ' ' + commonStyles['apply-opacity-30']
|
|
114
|
+
setWrapperClassName(className)
|
|
115
|
+
} else {
|
|
116
|
+
setWrapperClassName(normalClassName())
|
|
117
|
+
}
|
|
118
|
+
}, [disabled])
|
|
119
|
+
|
|
120
|
+
function renderLi (option, index) {
|
|
121
|
+
return (
|
|
122
|
+
<li
|
|
123
|
+
key={index} className={singleOptionClassName} onClick={() => {
|
|
124
|
+
if (option.notSelectable) {
|
|
125
|
+
return handleNotSelectable(option.onClick && option.onClick())
|
|
126
|
+
}
|
|
127
|
+
return setSelected(option)
|
|
128
|
+
}}
|
|
129
|
+
>
|
|
130
|
+
<div className={styles.liContent}>
|
|
131
|
+
{option.iconName && <PlatformaticIcon iconName={option.iconName} color={option.iconColor || mainColor} size={option.iconSize ?? SMALL} onClick={null} />}
|
|
132
|
+
<span className={optionSpanClassName}>{option.label}</span>
|
|
133
|
+
</div>
|
|
134
|
+
{option.descriptionValue && <span className={`${optionSpanClassName} ${styles.descriptionValue}`}>{option.descriptionValue}</span>}
|
|
135
|
+
</li>
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function renderOptions () {
|
|
140
|
+
if (value.length === 0) {
|
|
141
|
+
return (
|
|
142
|
+
<ul className={optionsClassName}>
|
|
143
|
+
{options.length > 0 ? options.map((option, index) => renderLi(option, index)) : <li className={singleOptionClassName}><div className={styles.liContent}><span className={commonStyles[`text--${mainColor}`]}>No data found</span></div></li>}
|
|
144
|
+
</ul>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
const notFilterableOptions = options.filter(option => option.notFilterable)
|
|
148
|
+
const filteredOptions = options.filter(option => !option.notFilterable).filter(option => option.label.toLowerCase().includes(value.toLowerCase()))
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<ul className={optionsClassName}>
|
|
152
|
+
{filteredOptions.length > 0 ? filteredOptions.map((option, index) => renderLi(option, index)) : <li className={singleOptionClassName}><div className={styles.liContent}><span className={commonStyles[`text--${mainColor}`]}>No data found</span></div></li>}
|
|
153
|
+
{notFilterableOptions.length > 0 && notFilterableOptions.map((option, index) => renderLi(option, index))}
|
|
154
|
+
</ul>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-change-or-input-event-in-react-js
|
|
159
|
+
function clearValue () {
|
|
160
|
+
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set
|
|
161
|
+
nativeInputValueSetter.call(inputRef.current, '')
|
|
162
|
+
const ev2 = new Event('input', { bubbles: true })
|
|
163
|
+
ev2.simulated = true
|
|
164
|
+
inputRef.current.dispatchEvent(ev2)
|
|
165
|
+
onClear()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function handleFocus () {
|
|
169
|
+
if (!isOnFocus) {
|
|
170
|
+
setIsOnFocus(true)
|
|
171
|
+
setWrapperClassName(onFocusClassName())
|
|
172
|
+
}
|
|
173
|
+
setShowOptions(true)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function handleBlur (event) {
|
|
177
|
+
event.preventDefault()
|
|
178
|
+
setTimeout(() => {
|
|
179
|
+
if (showOptions) {
|
|
180
|
+
setShowOptions(false)
|
|
181
|
+
setIsOnFocus(false)
|
|
182
|
+
setWrapperClassName(normalClassName())
|
|
183
|
+
}
|
|
184
|
+
}, 250)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<div className={containerClassName} {...dataProps}>
|
|
189
|
+
<div className={styles.selectContainer}>
|
|
190
|
+
<input type='text' name={name} value={value} className={wrapperClassName} ref={inputRef} onChange={onChange} disabled={disabled} placeholder={placeholder} onFocus={() => handleFocus()} onBlur={(e) => handleBlur(e)} />
|
|
191
|
+
<div className={styles.icons}>
|
|
192
|
+
{value?.length > 0 && !disabled && <PlatformaticIcon iconName='CircleCloseIcon' color={borderColor} onClick={() => clearValue()} size={SMALL} />}
|
|
193
|
+
<PlatformaticIcon iconName={showOptions ? 'ArrowUpIcon' : 'ArrowDownIcon'} color={borderColor} disabled={disabled} onClick={() => setShowOptions(!showOptions)} size={SMALL} />
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
{showOptions && !showError && renderOptions()}
|
|
197
|
+
{showError && <span className={commonStyles['error-message']}>{errorMessage}</span>}
|
|
198
|
+
</div>
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
SelectWithInput.propTypes = {
|
|
203
|
+
/**
|
|
204
|
+
* defaultContainerClassName
|
|
205
|
+
*/
|
|
206
|
+
defaultContainerClassName: PropTypes.string,
|
|
207
|
+
/**
|
|
208
|
+
* placeholder
|
|
209
|
+
*/
|
|
210
|
+
placeholder: PropTypes.string,
|
|
211
|
+
/**
|
|
212
|
+
* name
|
|
213
|
+
*/
|
|
214
|
+
name: PropTypes.oneOfType([
|
|
215
|
+
PropTypes.string,
|
|
216
|
+
PropTypes.number
|
|
217
|
+
]),
|
|
218
|
+
/**
|
|
219
|
+
* value
|
|
220
|
+
*/
|
|
221
|
+
value: PropTypes.string,
|
|
222
|
+
/**
|
|
223
|
+
* options
|
|
224
|
+
*/
|
|
225
|
+
options: PropTypes.arrayOf(PropTypes.shape({
|
|
226
|
+
label: PropTypes.string,
|
|
227
|
+
value: PropTypes.oneOfType([
|
|
228
|
+
PropTypes.string,
|
|
229
|
+
PropTypes.number
|
|
230
|
+
]),
|
|
231
|
+
iconName: PropTypes.string,
|
|
232
|
+
iconSize: PropTypes.string,
|
|
233
|
+
iconColor: PropTypes.string,
|
|
234
|
+
notSelectable: PropTypes.bool,
|
|
235
|
+
notFilterable: PropTypes.bool,
|
|
236
|
+
onClick: PropTypes.func,
|
|
237
|
+
descriptionValue: PropTypes.string
|
|
238
|
+
})),
|
|
239
|
+
/**
|
|
240
|
+
* defaultOptionsClassName
|
|
241
|
+
*/
|
|
242
|
+
defaultOptionsClassName: PropTypes.string,
|
|
243
|
+
/**
|
|
244
|
+
* optionsBorderedBottom
|
|
245
|
+
*/
|
|
246
|
+
optionsBorderedBottom: PropTypes.bool,
|
|
247
|
+
/**
|
|
248
|
+
* color of border
|
|
249
|
+
*/
|
|
250
|
+
borderColor: PropTypes.oneOf([MAIN_GREEN, MAIN_DARK_BLUE, WHITE]),
|
|
251
|
+
/**
|
|
252
|
+
* color of border UL
|
|
253
|
+
*/
|
|
254
|
+
borderListColor: PropTypes.oneOf([MAIN_GREEN, MAIN_DARK_BLUE, WHITE]),
|
|
255
|
+
/**
|
|
256
|
+
* onChange
|
|
257
|
+
*/
|
|
258
|
+
onChange: PropTypes.func,
|
|
259
|
+
/**
|
|
260
|
+
* onSelect
|
|
261
|
+
*/
|
|
262
|
+
onSelect: PropTypes.func,
|
|
263
|
+
/**
|
|
264
|
+
* onClear
|
|
265
|
+
*/
|
|
266
|
+
onClear: PropTypes.func,
|
|
267
|
+
/**
|
|
268
|
+
* Disabled
|
|
269
|
+
*/
|
|
270
|
+
disabled: PropTypes.bool,
|
|
271
|
+
/**
|
|
272
|
+
* mainColor
|
|
273
|
+
*/
|
|
274
|
+
mainColor: PropTypes.oneOf([MAIN_DARK_BLUE, WHITE]),
|
|
275
|
+
/**
|
|
276
|
+
* optionSelected
|
|
277
|
+
*/
|
|
278
|
+
optionSelected: PropTypes.shape({
|
|
279
|
+
label: PropTypes.string,
|
|
280
|
+
value: PropTypes.oneOfType([
|
|
281
|
+
PropTypes.string,
|
|
282
|
+
PropTypes.number
|
|
283
|
+
])
|
|
284
|
+
}),
|
|
285
|
+
/**
|
|
286
|
+
* dataAttrName
|
|
287
|
+
*/
|
|
288
|
+
dataAttrName: PropTypes.string,
|
|
289
|
+
/**
|
|
290
|
+
* dataAttrValue
|
|
291
|
+
*/
|
|
292
|
+
dataAttrValue: PropTypes.string,
|
|
293
|
+
/**
|
|
294
|
+
* backgroundColor
|
|
295
|
+
*/
|
|
296
|
+
backgroundColor: PropTypes.oneOf([MAIN_DARK_BLUE, WHITE, RICH_BLACK]),
|
|
297
|
+
/**
|
|
298
|
+
* inputTextClassName
|
|
299
|
+
*/
|
|
300
|
+
inputTextClassName: PropTypes.string
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
SelectWithInput.defaultProps = {
|
|
304
|
+
defaultContainerClassName: '',
|
|
305
|
+
placeholder: 'this is the default',
|
|
306
|
+
name: '',
|
|
307
|
+
value: '',
|
|
308
|
+
id: '',
|
|
309
|
+
options: [],
|
|
310
|
+
defaultOptionsClassName: '',
|
|
311
|
+
optionsBorderedBottom: true,
|
|
312
|
+
borderColor: MAIN_GREEN,
|
|
313
|
+
borderListColor: '',
|
|
314
|
+
errorMessage: '',
|
|
315
|
+
onChange: () => {},
|
|
316
|
+
onSelect: () => {},
|
|
317
|
+
onClear: () => {},
|
|
318
|
+
disabled: false,
|
|
319
|
+
mainColor: MAIN_DARK_BLUE,
|
|
320
|
+
optionSelected: null,
|
|
321
|
+
dataAttrName: '',
|
|
322
|
+
dataAttrValue: '',
|
|
323
|
+
backgroundColor: WHITE,
|
|
324
|
+
inputTextClassName: ''
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export default SelectWithInput
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
@apply flex flex-col w-full relative
|
|
3
|
+
}
|
|
4
|
+
.selectContainer {
|
|
5
|
+
@apply w-full flex items-center relative z-10;
|
|
6
|
+
}
|
|
7
|
+
.select {
|
|
8
|
+
@apply py-1.5 px-2 md:px-3 md:py-[10.5px] h-full border border-solid box-border rounded;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.select-main-dark-blue.active,
|
|
12
|
+
.select-main-dark-blue:focus {
|
|
13
|
+
@apply shadow-main-dark-blue outline-none;
|
|
14
|
+
}
|
|
15
|
+
.select-white.active,
|
|
16
|
+
.select-white:focus {
|
|
17
|
+
@apply outline-none;
|
|
18
|
+
}
|
|
19
|
+
.options {
|
|
20
|
+
@apply absolute left-0 top-[42px] w-full max-h-[216px] overflow-y-scroll z-20;
|
|
21
|
+
}
|
|
22
|
+
.bordered-options {
|
|
23
|
+
@apply border border-solid rounded
|
|
24
|
+
}
|
|
25
|
+
.option {
|
|
26
|
+
@apply font-light cursor-pointer z-10 flex flex-row justify-between items-center px-4;
|
|
27
|
+
}
|
|
28
|
+
.bordered-bottom {
|
|
29
|
+
@apply border-b
|
|
30
|
+
}
|
|
31
|
+
.icons {
|
|
32
|
+
@apply absolute top-[50%] right-[0.5rem] flex gap-x-2;
|
|
33
|
+
transform: translateY(-50%);
|
|
34
|
+
}
|
|
35
|
+
.liContent {
|
|
36
|
+
@apply py-2 flex items-center gap-x-1 relative z-[-1] w-auto basis-2/3;
|
|
37
|
+
}
|
|
38
|
+
.descriptionValue {
|
|
39
|
+
@apply opacity-70
|
|
40
|
+
}
|
|
@@ -5,6 +5,7 @@ import Password from './Password'
|
|
|
5
5
|
import Preview from './Preview'
|
|
6
6
|
import RadioGroup from './RadioGroup'
|
|
7
7
|
import Select from './Select'
|
|
8
|
+
import SelectWithInput from './SelectWithInput'
|
|
8
9
|
import TextArea from './TextArea'
|
|
9
10
|
import ToggleSwitch from './ToggleSwitch'
|
|
10
11
|
|
|
@@ -16,6 +17,7 @@ export default {
|
|
|
16
17
|
Preview,
|
|
17
18
|
RadioGroup,
|
|
18
19
|
Select,
|
|
20
|
+
SelectWithInput,
|
|
19
21
|
TextArea,
|
|
20
22
|
ToggleSwitch
|
|
21
23
|
}
|
|
@@ -35,18 +35,13 @@ Default.args = {
|
|
|
35
35
|
const TemplateBorderMainDarkBlue = (args) => {
|
|
36
36
|
const [value, setValue] = useState('')
|
|
37
37
|
|
|
38
|
-
function handleChange (event) {
|
|
39
|
-
setValue(event.target.value)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
38
|
function handleSelect (event) {
|
|
43
39
|
setValue(event.detail.label)
|
|
44
40
|
}
|
|
45
41
|
|
|
46
42
|
return (
|
|
47
43
|
<div style={{ backgroundColor: 'white', padding: '20px 10px' }}>
|
|
48
|
-
<
|
|
49
|
-
<Select {...args} value={value} onChange={handleChange} onSelect={handleSelect} />
|
|
44
|
+
<Select {...args} value={value} onSelect={handleSelect} />
|
|
50
45
|
</div>
|
|
51
46
|
)
|
|
52
47
|
}
|
|
@@ -54,10 +49,6 @@ const TemplateBorderMainDarkBlue = (args) => {
|
|
|
54
49
|
const TemplateBorderMainDarkBlue2 = (args) => {
|
|
55
50
|
const [value, setValue] = useState('')
|
|
56
51
|
|
|
57
|
-
function handleChange (event) {
|
|
58
|
-
setValue(event.target.value)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
52
|
function handleSelect (event) {
|
|
62
53
|
setValue(event.detail.label)
|
|
63
54
|
}
|
|
@@ -66,7 +57,7 @@ const TemplateBorderMainDarkBlue2 = (args) => {
|
|
|
66
57
|
<>
|
|
67
58
|
<div style={{ backgroundColor: 'white', padding: '20px 10px' }}>
|
|
68
59
|
<p>Value of the input {value}</p>
|
|
69
|
-
<Select {...args} value={value}
|
|
60
|
+
<Select {...args} value={value} onSelect={handleSelect} />
|
|
70
61
|
<br />
|
|
71
62
|
<Select placeholder='Disabled' disabled />
|
|
72
63
|
|
|
@@ -146,10 +137,6 @@ githubRepoExample.args = {
|
|
|
146
137
|
const TemplateTransparent = (args) => {
|
|
147
138
|
const [value, setValue] = useState('')
|
|
148
139
|
|
|
149
|
-
function handleChange (event) {
|
|
150
|
-
setValue(event.target.value)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
140
|
function handleSelect (event) {
|
|
154
141
|
setValue(event.detail.label)
|
|
155
142
|
}
|
|
@@ -157,7 +144,7 @@ const TemplateTransparent = (args) => {
|
|
|
157
144
|
return (
|
|
158
145
|
<div style={{ padding: '20px 10px' }}>
|
|
159
146
|
<p>Value of the input {value}</p>
|
|
160
|
-
<Select {...args} value={value}
|
|
147
|
+
<Select {...args} value={value} onSelect={handleSelect} />
|
|
161
148
|
</div>
|
|
162
149
|
)
|
|
163
150
|
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
import React, { useState } from 'react'
|
|
3
|
+
import SelectWithInput from '../../components/forms/SelectWithInput'
|
|
4
|
+
import { MAIN_DARK_BLUE, MAIN_GREEN, RICH_BLACK, WHITE } from '../../components/constants'
|
|
5
|
+
|
|
6
|
+
const divStyle = {
|
|
7
|
+
width: '100%',
|
|
8
|
+
height: 'auto',
|
|
9
|
+
padding: '20px',
|
|
10
|
+
backgroundColor: 'transparent'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default {
|
|
14
|
+
title: 'Platformatic/Forms/SelectWithInput',
|
|
15
|
+
component: SelectWithInput,
|
|
16
|
+
decorators: [
|
|
17
|
+
(Story) => (
|
|
18
|
+
<div style={divStyle}>
|
|
19
|
+
<Story />
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const Template = (args) => <SelectWithInput {...args} />
|
|
26
|
+
|
|
27
|
+
export const Default = Template.bind({})
|
|
28
|
+
|
|
29
|
+
Default.args = {
|
|
30
|
+
name: 'test',
|
|
31
|
+
placeholder: 'Platformatic empty select',
|
|
32
|
+
borderColor: MAIN_GREEN
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const TemplateBorderMainDarkBlue = (args) => {
|
|
36
|
+
const [value, setValue] = useState('')
|
|
37
|
+
|
|
38
|
+
function handleChange (event) {
|
|
39
|
+
setValue(event.target.value)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function handleSelect (event) {
|
|
43
|
+
setValue(event.detail.label)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div style={{ backgroundColor: 'white', padding: '20px 10px' }}>
|
|
48
|
+
<p>Value of the input {value}</p>
|
|
49
|
+
<SelectWithInput {...args} value={value} onChange={handleChange} onSelect={handleSelect} />
|
|
50
|
+
</div>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const TemplateBorderMainDarkBlue2 = (args) => {
|
|
55
|
+
const [value, setValue] = useState('')
|
|
56
|
+
|
|
57
|
+
function handleChange (event) {
|
|
58
|
+
setValue(event.target.value)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function handleSelect (event) {
|
|
62
|
+
setValue(event.detail.label)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<>
|
|
67
|
+
<div style={{ backgroundColor: 'white', padding: '20px 10px' }}>
|
|
68
|
+
<p>Value of the input {value}</p>
|
|
69
|
+
<SelectWithInput {...args} value={value} onChange={handleChange} onSelect={handleSelect} />
|
|
70
|
+
<br />
|
|
71
|
+
<SelectWithInput placeholder='Disabled' disabled />
|
|
72
|
+
|
|
73
|
+
</div>
|
|
74
|
+
</>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const BorderMainDarkBlue = TemplateBorderMainDarkBlue.bind({})
|
|
79
|
+
|
|
80
|
+
BorderMainDarkBlue.args = {
|
|
81
|
+
name: 'test',
|
|
82
|
+
placeholder: 'Defaul option',
|
|
83
|
+
options: [...Array(20).keys()].map(ele => ({ label: `Option ${ele}`, value: `Value${ele}`, iconName: 'SocialGitHubIcon' })),
|
|
84
|
+
borderColor: MAIN_DARK_BLUE
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const DefaultInvalid = TemplateBorderMainDarkBlue.bind({})
|
|
88
|
+
|
|
89
|
+
DefaultInvalid.args = {
|
|
90
|
+
name: 'test',
|
|
91
|
+
placeholder: 'Platformatic empty select',
|
|
92
|
+
options: [{ label: 'Option 1', value: '1' }, { label: 'Option 2', value: '2' }, { label: 'Option 3', value: '3' }],
|
|
93
|
+
borderColor: MAIN_GREEN,
|
|
94
|
+
errorMessage: 'This is an error message'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const withDifferentOptions = TemplateBorderMainDarkBlue2.bind({})
|
|
98
|
+
|
|
99
|
+
withDifferentOptions.args = {
|
|
100
|
+
name: 'test',
|
|
101
|
+
placeholder: 'Platformatic',
|
|
102
|
+
options: [...Array(20).keys()].map(ele => ({
|
|
103
|
+
label: `Option ${ele}`,
|
|
104
|
+
value: `Value${ele}`,
|
|
105
|
+
iconName: 'SocialGitHubIcon'
|
|
106
|
+
})).concat({
|
|
107
|
+
label: 'Last but not least',
|
|
108
|
+
iconName: 'AddIcon',
|
|
109
|
+
value: 123123,
|
|
110
|
+
notSelectable: true,
|
|
111
|
+
onClick: () => alert('you clicked last but not least')
|
|
112
|
+
}).concat({
|
|
113
|
+
label: 'Play',
|
|
114
|
+
iconName: 'PlayIcon',
|
|
115
|
+
value: 123123,
|
|
116
|
+
notSelectable: true
|
|
117
|
+
}),
|
|
118
|
+
borderColor: MAIN_DARK_BLUE
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const githubRepoExample = TemplateBorderMainDarkBlue2.bind({})
|
|
122
|
+
|
|
123
|
+
githubRepoExample.args = {
|
|
124
|
+
name: 'test',
|
|
125
|
+
placeholder: 'Platformatic',
|
|
126
|
+
options: [...Array(4).keys()].map(ele => ({
|
|
127
|
+
label: `GitHub Repo ${ele}`,
|
|
128
|
+
value: `Value${ele}`,
|
|
129
|
+
iconName: 'SocialGitHubIcon'
|
|
130
|
+
})).concat({
|
|
131
|
+
label: 'this is an option that is not selectable',
|
|
132
|
+
iconName: 'AddIcon',
|
|
133
|
+
value: 123123,
|
|
134
|
+
notSelectable: true,
|
|
135
|
+
onClick: () => alert('you clicked last but not least')
|
|
136
|
+
}).concat({
|
|
137
|
+
label: 'Add Github repo',
|
|
138
|
+
iconName: 'PlayIcon',
|
|
139
|
+
value: 123123,
|
|
140
|
+
notSelectable: true,
|
|
141
|
+
notFilterable: true
|
|
142
|
+
}),
|
|
143
|
+
borderColor: MAIN_DARK_BLUE
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const TemplateTransparent = (args) => {
|
|
147
|
+
const [value, setValue] = useState('')
|
|
148
|
+
|
|
149
|
+
function handleChange (event) {
|
|
150
|
+
setValue(event.target.value)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function handleSelect (event) {
|
|
154
|
+
setValue(event.detail.label)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div style={{ padding: '20px 10px' }}>
|
|
159
|
+
<p>Value of the input {value}</p>
|
|
160
|
+
<SelectWithInput {...args} value={value} onChange={handleChange} onSelect={handleSelect} />
|
|
161
|
+
</div>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export const TemplateBackgroundTransparent = TemplateTransparent.bind({})
|
|
166
|
+
|
|
167
|
+
TemplateBackgroundTransparent.args = {
|
|
168
|
+
name: 'test',
|
|
169
|
+
placeholder: 'Defaul option',
|
|
170
|
+
optionsBorderedBottom: false,
|
|
171
|
+
options: [...Array(20).keys()].map(ele => ({ label: `Option ${ele}`, value: `Value${ele}`, iconName: 'OrganizationIcon' })),
|
|
172
|
+
borderColor: WHITE,
|
|
173
|
+
mainColor: WHITE,
|
|
174
|
+
backgroundColor: RICH_BLACK,
|
|
175
|
+
borderListColor: WHITE
|
|
176
|
+
}
|