@loadsmart/loadsmart-ui 5.6.3 → 5.8.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/README.md +13 -7
- package/dist/components/Dropdown/Dropdown.types.d.ts +6 -0
- package/dist/components/Dropdown/useDropdown.d.ts +1 -1
- package/dist/components/Select/Select.stories.d.ts +1 -0
- package/dist/components/Select/Select.types.d.ts +4 -0
- package/dist/index.js +173 -173
- package/dist/index.js.map +1 -1
- package/dist/{prop-0c635ee9.js → prop-0f94ff83.js} +1 -1
- package/dist/{prop-0c635ee9.js.map → prop-0f94ff83.js.map} +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/theming/index.d.ts +2 -1
- package/dist/theming/index.js.map +1 -1
- package/dist/tools/index.js +1 -1
- package/package.json +3 -5
- package/src/components/Dropdown/Dropdown.context.ts +1 -0
- package/src/components/Dropdown/Dropdown.tsx +12 -4
- package/src/components/Dropdown/Dropdown.types.ts +6 -0
- package/src/components/Dropdown/DropdownTrigger.tsx +5 -4
- package/src/components/Dropdown/useDropdown.ts +9 -8
- package/src/components/Select/Select.fixtures.ts +1 -1
- package/src/components/Select/Select.stories.tsx +98 -15
- package/src/components/Select/Select.test.tsx +105 -9
- package/src/components/Select/Select.tsx +14 -8
- package/src/components/Select/Select.types.ts +5 -0
- package/src/components/Select/useSelect.ts +53 -15
- package/src/stories/startPage.stories.mdx +20 -8
- package/src/testing/SelectEvent/SelectEvent.ts +4 -0
- package/src/theming/index.ts +11 -5
|
@@ -105,6 +105,10 @@ const DividerText = styled(Text)`
|
|
|
105
105
|
background-color: ${token('color-neutral-white')};
|
|
106
106
|
`
|
|
107
107
|
|
|
108
|
+
function isQuerying(status: SelectStatus) {
|
|
109
|
+
return ['pending-query', 'querying'].includes(status)
|
|
110
|
+
}
|
|
111
|
+
|
|
108
112
|
type SeparatorProps = {
|
|
109
113
|
status: SelectStatus
|
|
110
114
|
after?: unknown[]
|
|
@@ -112,7 +116,7 @@ type SeparatorProps = {
|
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
function Separator({ status, after = [], before = [] }: SeparatorProps): JSX.Element {
|
|
115
|
-
if (status
|
|
119
|
+
if (!isQuerying(status)) {
|
|
116
120
|
if (isEmpty(after) && !isEmpty(before)) return <Divider />
|
|
117
121
|
if (isEmpty(after)) return <Fragment />
|
|
118
122
|
}
|
|
@@ -121,7 +125,7 @@ function Separator({ status, after = [], before = [] }: SeparatorProps): JSX.Ele
|
|
|
121
125
|
<div style={{ position: 'relative' }}>
|
|
122
126
|
<Divider />
|
|
123
127
|
<DividerText variant="caption-bold" color="color-neutral-light">
|
|
124
|
-
{status
|
|
128
|
+
{isQuerying(status) ? 'Loading...' : `${after.length} option${pluralize(after.length)}`}
|
|
125
129
|
</DividerText>
|
|
126
130
|
</div>
|
|
127
131
|
)
|
|
@@ -131,7 +135,7 @@ function renderOptionsSingle(select: useSelectReturn, components?: Components):
|
|
|
131
135
|
const { Option, Empty, CreatableOption } = getComponents(components)
|
|
132
136
|
const isCreatable = select.isCreatable()
|
|
133
137
|
|
|
134
|
-
if (select.status
|
|
138
|
+
if (isQuerying(select.status) && isEmpty(select.options)) {
|
|
135
139
|
return <SelectEmpty>Loading...</SelectEmpty>
|
|
136
140
|
}
|
|
137
141
|
|
|
@@ -145,11 +149,12 @@ function renderOptionsSingle(select: useSelectReturn, components?: Components):
|
|
|
145
149
|
|
|
146
150
|
return (
|
|
147
151
|
<>
|
|
152
|
+
{select.createOptionPosition === 'first' && isCreatable ? <CreatableOption /> : null}
|
|
148
153
|
{select.options.map((option) => {
|
|
149
154
|
const { value } = select.getSelectableOption(option)
|
|
150
155
|
return <Option key={String(value)} value={value} />
|
|
151
156
|
})}
|
|
152
|
-
{
|
|
157
|
+
{select.createOptionPosition === 'last' && isCreatable ? <CreatableOption /> : null}
|
|
153
158
|
</>
|
|
154
159
|
)
|
|
155
160
|
}
|
|
@@ -174,18 +179,19 @@ function renderOptionsMultiple(select: useSelectReturn, components?: Components)
|
|
|
174
179
|
|
|
175
180
|
let remaining = (
|
|
176
181
|
<Fragment>
|
|
182
|
+
{select.createOptionPosition === 'first' && isCreatable ? <CreatableOption /> : null}
|
|
177
183
|
{remainingOptions.map((option) => (
|
|
178
184
|
<Option key={String(option.value)} value={option.value} />
|
|
179
185
|
))}
|
|
180
|
-
{
|
|
186
|
+
{select.createOptionPosition === 'last' && isCreatable ? <CreatableOption /> : null}
|
|
181
187
|
</Fragment>
|
|
182
188
|
)
|
|
183
189
|
|
|
184
|
-
if (select.status
|
|
190
|
+
if (!isQuerying(select.status) && isEmpty(remainingOptions)) {
|
|
185
191
|
remaining = isCreatable ? <CreatableOption /> : <Empty>No more options.</Empty>
|
|
186
192
|
}
|
|
187
193
|
|
|
188
|
-
if (select.status
|
|
194
|
+
if (!isQuerying(select.status) && isEmpty(select.options)) {
|
|
189
195
|
remaining = isCreatable ? <CreatableOption /> : <Empty>No results found.</Empty>
|
|
190
196
|
}
|
|
191
197
|
|
|
@@ -214,7 +220,7 @@ function Select(props: SelectProps): JSX.Element {
|
|
|
214
220
|
}
|
|
215
221
|
|
|
216
222
|
function getTrailing() {
|
|
217
|
-
if (select.status
|
|
223
|
+
if (isQuerying(select.status)) {
|
|
218
224
|
return <Loading data-testid="select-trigger-loading">···</Loading>
|
|
219
225
|
}
|
|
220
226
|
|
|
@@ -67,6 +67,8 @@ export type Components = {
|
|
|
67
67
|
CreatableOption?: CreatableOptionType
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
export type CreateOptionPosition = 'first' | 'last'
|
|
71
|
+
|
|
70
72
|
export interface SelectProps extends DropdownProps {
|
|
71
73
|
name: string
|
|
72
74
|
placeholder?: string
|
|
@@ -79,6 +81,8 @@ export interface SelectProps extends DropdownProps {
|
|
|
79
81
|
onChange?: (event: EventLike<Option | Option[] | null>) => void
|
|
80
82
|
onCreate?: (query: string) => Promise<void | Option> | void | Option
|
|
81
83
|
onQueryChange?: (e: ChangeEvent<HTMLInputElement>) => void
|
|
84
|
+
isValidNewOption?: ((query: string) => boolean) | boolean
|
|
85
|
+
createOptionPosition?: CreateOptionPosition
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
export type SelectOptionProps = {
|
|
@@ -123,6 +127,7 @@ export type useSelectReturn = {
|
|
|
123
127
|
}
|
|
124
128
|
getCreatebleProps: () => CreatableProps
|
|
125
129
|
isCreatable: () => boolean
|
|
130
|
+
createOptionPosition?: CreateOptionPosition
|
|
126
131
|
}
|
|
127
132
|
|
|
128
133
|
export type useSelectExternalReturn = {
|
|
@@ -3,6 +3,7 @@ import { isFunction } from '@loadsmart/utils-function'
|
|
|
3
3
|
import { isNil } from '@loadsmart/utils-object'
|
|
4
4
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
5
5
|
|
|
6
|
+
import isEmpty from 'utils/toolset/isEmpty'
|
|
6
7
|
import { useDropdown } from 'components/Dropdown'
|
|
7
8
|
import { useDidMount } from 'hooks/useDidMount'
|
|
8
9
|
import { useFocusTrap } from 'hooks/useFocusTrap'
|
|
@@ -191,8 +192,18 @@ function useOptions<T = any>(props: { datasources: SelectDatasource<T>[]; adapte
|
|
|
191
192
|
*/
|
|
192
193
|
function useSelect(props: SelectProps): useSelectReturn {
|
|
193
194
|
const didMount = useDidMount()
|
|
194
|
-
const {
|
|
195
|
-
|
|
195
|
+
const {
|
|
196
|
+
multiple,
|
|
197
|
+
onQueryChange,
|
|
198
|
+
onChange,
|
|
199
|
+
onCreate,
|
|
200
|
+
id,
|
|
201
|
+
name,
|
|
202
|
+
disabled = false,
|
|
203
|
+
onBlur,
|
|
204
|
+
isValidNewOption = (query: string) => Boolean(query),
|
|
205
|
+
createOptionPosition = 'last',
|
|
206
|
+
} = props
|
|
196
207
|
|
|
197
208
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
198
209
|
const datasources = useMemo<SelectDatasource<any>[]>(() => getDatasources(props), [
|
|
@@ -221,11 +232,26 @@ function useSelect(props: SelectProps): useSelectReturn {
|
|
|
221
232
|
},
|
|
222
233
|
})
|
|
223
234
|
|
|
235
|
+
const [queryTyped, setQueryTyped] = useState(false)
|
|
224
236
|
const [query, setQuery] = useState<string>(
|
|
225
237
|
getDisplayValue(adapters, selectable.selected, multiple)
|
|
226
238
|
)
|
|
227
239
|
const options = useOptions({ datasources, adapters })
|
|
228
240
|
|
|
241
|
+
const expandDisabled = useMemo(
|
|
242
|
+
() => !query.length && isEmpty(options.get()) && isEmpty(selectable.selected),
|
|
243
|
+
[query, options, selectable.selected]
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
const dropdown = useDropdown({ ...props, expandDisabled })
|
|
247
|
+
|
|
248
|
+
useEffect(() => {
|
|
249
|
+
if (queryTyped) {
|
|
250
|
+
options.fetch(query)
|
|
251
|
+
dropdown.expand()
|
|
252
|
+
}
|
|
253
|
+
}, [query, queryTyped])
|
|
254
|
+
|
|
229
255
|
const getSelectableOption = useCallback(
|
|
230
256
|
function getSelectableOption(option: Option) {
|
|
231
257
|
const adapter = getAdapter(adapters, option._type)
|
|
@@ -274,9 +300,19 @@ function useSelect(props: SelectProps): useSelectReturn {
|
|
|
274
300
|
|
|
275
301
|
onBlur?.(event)
|
|
276
302
|
},
|
|
303
|
+
expandDisabled,
|
|
277
304
|
}
|
|
278
305
|
},
|
|
279
|
-
[
|
|
306
|
+
[
|
|
307
|
+
adapters,
|
|
308
|
+
dropdown.expanded,
|
|
309
|
+
dropdown.toggle,
|
|
310
|
+
multiple,
|
|
311
|
+
options,
|
|
312
|
+
selectable.selected,
|
|
313
|
+
onBlur,
|
|
314
|
+
expandDisabled,
|
|
315
|
+
]
|
|
280
316
|
)
|
|
281
317
|
|
|
282
318
|
const getTriggerProps = useCallback(
|
|
@@ -292,15 +328,13 @@ function useSelect(props: SelectProps): useSelectReturn {
|
|
|
292
328
|
onChange(e: ChangeEvent<HTMLInputElement>) {
|
|
293
329
|
onQueryChange?.(e)
|
|
294
330
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
dropdown.expand()
|
|
298
|
-
options.fetch(query)
|
|
331
|
+
setQuery(e.target.value)
|
|
332
|
+
setQueryTyped(true)
|
|
299
333
|
},
|
|
300
334
|
onFocus: TriggerOnFocusHandler,
|
|
301
335
|
}
|
|
302
336
|
},
|
|
303
|
-
[id, query, onQueryChange, dropdown, options]
|
|
337
|
+
[id, query, onQueryChange, dropdown, options, selectable.selected]
|
|
304
338
|
)
|
|
305
339
|
|
|
306
340
|
const getClearProps = useCallback(
|
|
@@ -396,14 +430,17 @@ function useSelect(props: SelectProps): useSelectReturn {
|
|
|
396
430
|
)
|
|
397
431
|
}
|
|
398
432
|
|
|
399
|
-
|
|
400
|
-
isFunction(
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
433
|
+
function getIsValidNewOption() {
|
|
434
|
+
if (isFunction(isValidNewOption)) {
|
|
435
|
+
return isValidNewOption(query) && !isQueryEqualAnOption()
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return isValidNewOption
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return isFunction(onCreate) && options.status === 'queried' && getIsValidNewOption()
|
|
405
442
|
},
|
|
406
|
-
[getSelectableOption, onCreate, options, query, selectable.selected]
|
|
443
|
+
[getSelectableOption, isValidNewOption, onCreate, options, query, selectable.selected]
|
|
407
444
|
)
|
|
408
445
|
|
|
409
446
|
useEffect(
|
|
@@ -461,6 +498,7 @@ function useSelect(props: SelectProps): useSelectReturn {
|
|
|
461
498
|
getDropdownProps,
|
|
462
499
|
getCreatebleProps,
|
|
463
500
|
isCreatable,
|
|
501
|
+
createOptionPosition,
|
|
464
502
|
}
|
|
465
503
|
}
|
|
466
504
|
|
|
@@ -2,20 +2,26 @@ import { Meta } from '@storybook/addon-docs/blocks'
|
|
|
2
2
|
|
|
3
3
|
<Meta title="Getting started page" />
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
Miranda UI is a [React](https://reactjs.org/) components library. It works with [Styled Components](https://www.styled-components.com).
|
|
7
6
|
|
|
8
7
|
## Steps to install
|
|
9
8
|
|
|
10
|
-
1.
|
|
11
|
-
|
|
9
|
+
1. You need to have node 16 or the latest LTS version installed;
|
|
10
|
+
|
|
11
|
+
One option is to use [NVM](https://github.com/nvm-sh/nvm). You can run `nvm use` or set it to run automatically in a directory with a .nvmrc file.
|
|
12
|
+
|
|
13
|
+
1. Install dependencies: `yarn`
|
|
14
|
+
|
|
15
|
+
If you don't have yarn, follow yarn [installation docs](https://classic.yarnpkg.com/lang/en/docs/install/#windows-stable)
|
|
12
16
|
|
|
13
17
|
## Usage
|
|
14
18
|
|
|
15
19
|
```sh
|
|
16
20
|
yarn add @loadsmart/loadsmart-ui
|
|
17
21
|
```
|
|
22
|
+
|
|
18
23
|
or
|
|
24
|
+
|
|
19
25
|
```sh
|
|
20
26
|
npm install @loadsmart/loadsmart-ui
|
|
21
27
|
```
|
|
@@ -43,7 +49,9 @@ Install all dependencies:
|
|
|
43
49
|
```bash
|
|
44
50
|
yarn
|
|
45
51
|
```
|
|
46
|
-
|
|
52
|
+
|
|
53
|
+
or
|
|
54
|
+
|
|
47
55
|
```bash
|
|
48
56
|
npm i
|
|
49
57
|
```
|
|
@@ -53,7 +61,9 @@ Run the application - you'll be able to see all components documentation in Stor
|
|
|
53
61
|
```bash
|
|
54
62
|
yarn dev
|
|
55
63
|
```
|
|
56
|
-
|
|
64
|
+
|
|
65
|
+
or
|
|
66
|
+
|
|
57
67
|
```bash
|
|
58
68
|
npm run dev
|
|
59
69
|
```
|
|
@@ -65,7 +75,9 @@ To run tests:
|
|
|
65
75
|
```bash
|
|
66
76
|
yarn test
|
|
67
77
|
```
|
|
68
|
-
|
|
78
|
+
|
|
79
|
+
or
|
|
80
|
+
|
|
69
81
|
```bash
|
|
70
82
|
npm run test
|
|
71
83
|
```
|
|
@@ -77,7 +89,7 @@ When creating a new one, you must add its new folder following this pattern.
|
|
|
77
89
|
|
|
78
90
|
It's also essential to include the `.stories` and `.test` files to cover both documentation and quality standards.
|
|
79
91
|
|
|
80
|
-
|
|
81
|
-
<br
|
|
92
|
+
<br />
|
|
93
|
+
<br />
|
|
82
94
|
|
|
83
95
|
We use [`semantic-release`](https://github.com/semantic-release/) to evaluate our commits and trigger automatic release to NPM. For that, please follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), so your changes will properly be evaluated and published if that's the case.
|
|
@@ -42,6 +42,10 @@ async function expand(input: HTMLElement): Promise<void> {
|
|
|
42
42
|
return
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
await waitFor(() => {
|
|
46
|
+
expect(within(selectContainer).getByTestId('select-trigger-handle')).toBeEnabled()
|
|
47
|
+
})
|
|
48
|
+
|
|
45
49
|
await act(async () => {
|
|
46
50
|
userEvent.click(within(selectContainer).getByTestId('select-trigger-handle'))
|
|
47
51
|
|
package/src/theming/index.ts
CHANGED
|
@@ -21,12 +21,18 @@ function getTokenFromTheme<P extends ThemedProps>(token: TokenLike<P>, props: P)
|
|
|
21
21
|
* @param {[ThemedStyledProps]} props - Component props.
|
|
22
22
|
* @returns {ThemeTokenValue} Token value or `undefined` if the token was not found for the current theme.
|
|
23
23
|
*/
|
|
24
|
-
export function getToken<
|
|
25
|
-
token: TokenLike<
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
export function getToken<TProps extends ThemedProps>(
|
|
25
|
+
token: TokenLike<TProps>
|
|
26
|
+
): (props: TProps) => ThemeTokenValue
|
|
27
|
+
|
|
28
|
+
export function getToken<TProps extends ThemedProps>(
|
|
29
|
+
token: TokenLike<TProps>,
|
|
30
|
+
props: TProps
|
|
31
|
+
): ThemeTokenValue
|
|
32
|
+
|
|
33
|
+
export function getToken<TProps extends ThemedProps>(token: any, props?: any): any {
|
|
28
34
|
if (props === undefined) {
|
|
29
|
-
return (props) => getTokenFromTheme(token, props)
|
|
35
|
+
return (props: TProps) => getTokenFromTheme(token, props)
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
return getTokenFromTheme(token, props)
|