@startupjs-ui/multi-select 0.1.3

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/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ ## [0.1.3](https://github.com/startupjs/startupjs-ui/compare/v0.1.2...v0.1.3) (2025-12-29)
7
+
8
+ **Note:** Version bump only for package @startupjs-ui/multi-select
9
+
10
+
11
+
12
+
13
+
14
+ ## [0.1.2](https://github.com/startupjs/startupjs-ui/compare/v0.1.1...v0.1.2) (2025-12-29)
15
+
16
+
17
+ ### Features
18
+
19
+ * add mdx and docs packages. Refactor docs to get rid of any @startupjs/ui usage and use startupjs-ui instead ([703c926](https://github.com/startupjs/startupjs-ui/commit/703c92636efb0421ffd11783f692fc892b74018f))
20
+ * **multi-select:** refactor MultiSelect component ([65ea9c9](https://github.com/startupjs/startupjs-ui/commit/65ea9c9b06c68c1058c92a6f775fed735d509cb4))
package/README.mdx ADDED
@@ -0,0 +1,234 @@
1
+ import { useState } from 'react'
2
+ import { Sandbox } from '@startupjs-ui/docs'
3
+ import Br from '@startupjs-ui/br'
4
+ import Div from '@startupjs-ui/div'
5
+ import Span from '@startupjs-ui/span'
6
+ import MultiSelect, { _PropsJsonSchema as MultiSelectPropsJsonSchema } from './index'
7
+
8
+ # MultiSelect
9
+
10
+ MultiSelect lets user pick multiple options.
11
+
12
+ ```jsx
13
+ import { MultiSelect } from 'startupjs-ui'
14
+ ```
15
+
16
+ ## Initialization
17
+
18
+ Before use you need to configure [Portal](/docs/components/Portal)
19
+
20
+ ## Simple example
21
+
22
+ ```jsx example
23
+ const OPTIONS = [
24
+ { label: 'New York', value: 'ny' },
25
+ { label: 'Los Angeles', value: 'la' },
26
+ { label: 'Tokyo', value: 'tk' },
27
+ ]
28
+
29
+ const [cities, setCities] = useState([])
30
+
31
+ return (
32
+ <MultiSelect
33
+ value={cities}
34
+ onChange={setCities}
35
+ options={OPTIONS}
36
+ />
37
+ )
38
+ ```
39
+
40
+ ## Options
41
+
42
+ You can pass array of objects `{ label, value }` or primitives to `options`.
43
+
44
+ ```jsx example
45
+ const OBJECT_OPTIONS_EXAMPLE = [
46
+ { label: 'New York', value: 'ny' },
47
+ { label: 'Los Angeles', value: 'la' },
48
+ ]
49
+ const PRIMITIVIES_OPTIONS_EXAMPLE = [
50
+ 'New York',
51
+ 'Los Angeles',
52
+ ]
53
+
54
+ const [cities, setCities] = useState([])
55
+ const [citiesArray, setCitiesArray] = useState([])
56
+
57
+ return (
58
+ <>
59
+ <MultiSelect
60
+ value={cities}
61
+ onChange={setCities}
62
+ options={OBJECT_OPTIONS_EXAMPLE}
63
+ />
64
+ <Br />
65
+ <MultiSelect
66
+ value={citiesArray}
67
+ onChange={setCitiesArray}
68
+ options={PRIMITIVIES_OPTIONS_EXAMPLE}
69
+ />
70
+ </>
71
+ )
72
+ ```
73
+
74
+ ## Disabled
75
+
76
+ ```jsx example
77
+ const OPTIONS = [
78
+ { label: 'New York', value: 'ny' },
79
+ { label: 'Los Angeles', value: 'la' },
80
+ { label: 'Tokyo', value: 'tk' },
81
+ ]
82
+ const [cities, setCities] = useState(['ny'])
83
+
84
+ return (
85
+ <MultiSelect
86
+ disabled
87
+ value={cities}
88
+ onChange={setCities}
89
+ options={OPTIONS}
90
+ />
91
+ )
92
+ ```
93
+
94
+ ## Readonly
95
+
96
+ ```jsx example
97
+ const OPTIONS = [
98
+ { label: 'New York', value: 'ny' },
99
+ { label: 'Los Angeles', value: 'la' },
100
+ { label: 'Tokyo', value: 'tk' },
101
+ ]
102
+ const [cities, setCities] = useState(['ny'])
103
+
104
+ return (
105
+ <MultiSelect
106
+ readonly
107
+ value={cities}
108
+ onChange={setCities}
109
+ options={OPTIONS}
110
+ />
111
+ )
112
+ ```
113
+
114
+ ## Tag
115
+
116
+ You can add a custom tag component.
117
+
118
+ ```jsx example
119
+ const OPTIONS = [
120
+ { label: 'New York', value: 'ny' },
121
+ { label: 'Los Angeles', value: 'la' },
122
+ { label: 'Tokyo', value: 'tk' },
123
+ ]
124
+
125
+ const [cities, setCities] = useState([])
126
+
127
+ return (
128
+ <MultiSelect
129
+ value={cities}
130
+ TagComponent={({ record }) => <Span>✔ {record.label} &nbsp;&nbsp;&nbsp;&nbsp;</Span>}
131
+ onChange={setCities}
132
+ options={OPTIONS}
133
+ />
134
+ )
135
+ ```
136
+
137
+ ## Input Component
138
+
139
+ You can add a custom input component.
140
+
141
+ ```jsx example
142
+ const OPTIONS = [
143
+ { label: 'New York', value: 'ny' },
144
+ { label: 'Los Angeles', value: 'la' },
145
+ { label: 'Tokyo', value: 'tk' },
146
+ ]
147
+
148
+ const [cities, setCities] = useState([])
149
+
150
+ return (
151
+ <MultiSelect
152
+ value={cities}
153
+ InputComponent={
154
+ ({ children, onOpen }) => {
155
+ return (
156
+ <Div
157
+ onPress={onOpen}
158
+ style={{
159
+ border: '1px dotted green',
160
+ padding: 8
161
+ }}
162
+ >
163
+ {children}
164
+ </Div>
165
+ )
166
+ }
167
+ }
168
+ TagComponent={({ record }) => <Span>✔ {record.label} &nbsp;&nbsp;&nbsp;&nbsp;</Span>}
169
+ onChange={setCities}
170
+ options={OPTIONS}
171
+ />
172
+ )
173
+ ```
174
+
175
+ ## Tag limit
176
+
177
+ You can limit the number of displayed tags by passing `tagLimit={number}`.
178
+
179
+ ```jsx example
180
+ const OPTIONS = [
181
+ { label: 'New York', value: 'ny' },
182
+ { label: 'Los Angeles', value: 'la' },
183
+ { label: 'Tokyo', value: 'tk' },
184
+ ]
185
+
186
+ const [cities, setCities] = useState([])
187
+
188
+ return (
189
+ <MultiSelect
190
+ tagLimit={2}
191
+ value={cities}
192
+ onChange={setCities}
193
+ options={OPTIONS}
194
+ />
195
+ )
196
+ ```
197
+
198
+ ## Limit the number of selected tags
199
+
200
+ You can limit the number of selected tags by passing `maxTagCount={number}`.
201
+
202
+ ```jsx example
203
+ const OPTIONS = [
204
+ { label: 'New York', value: 'ny' },
205
+ { label: 'Los Angeles', value: 'la' },
206
+ { label: 'Tokyo', value: 'tk' },
207
+ ]
208
+
209
+ const [cities, setCities] = useState([])
210
+
211
+ return (
212
+ <MultiSelect
213
+ maxTagCount={2}
214
+ value={cities}
215
+ onChange={setCities}
216
+ options={OPTIONS}
217
+ />
218
+ )
219
+ ```
220
+
221
+ ## Sandbox
222
+
223
+ <Sandbox
224
+ Component={MultiSelect}
225
+ propsJsonSchema={MultiSelectPropsJsonSchema}
226
+ props={{
227
+ value: ['New York'],
228
+ options: ['New York', 'Los Angeles', 'Tokyo'],
229
+ onChange: value => console.info('New value is ' + JSON.stringify(value)),
230
+ onSelect: value => console.info('Value \"' + value + '\" is selected'),
231
+ onRemove: value => console.info('Value \"' + value + '\" is removed'),
232
+ }}
233
+ block
234
+ />
@@ -0,0 +1,95 @@
1
+ $inputBg = var(--color-bg-main-strong)
2
+ $inputBorderWidth = 1px
3
+ $inputBorderColor = var(--color-border-main)
4
+ $inputBgDisabled = var(--color-bg-main)
5
+ $inputPlaceholderColor = var(--color-text-placeholder)
6
+ $focusedColor = var(--color-border-primary)
7
+ $errorColor = var(--color-border-error)
8
+ $swipeZoneColor = var(--color-bg-secondary-subtle)
9
+ $suggestionsPopoverMaxHeight = 25u
10
+
11
+ .input
12
+ padding 5px 1u
13
+ background-color $inputBg
14
+ border-width $inputBorderWidth
15
+ border-style solid
16
+ border-color $inputBorderColor
17
+ min-height 4u
18
+ min-width 8u
19
+ radius()
20
+
21
+ &.disabled
22
+ background-color $inputBgDisabled
23
+
24
+ &.focused
25
+ border-color $focusedColor
26
+
27
+ &.error
28
+ border-color $errorColor
29
+
30
+ .placeholder
31
+ color $inputPlaceholderColor
32
+
33
+ .tag
34
+ margin-right 0.5u
35
+
36
+ &.last
37
+ margin-right 0
38
+
39
+ .row
40
+ padding-right 3u
41
+ width 100%
42
+
43
+ .suggestions-web
44
+ padding 0 1u
45
+ max-height $suggestionsPopoverMaxHeight
46
+
47
+ .suggestions-native
48
+ padding 1u 0
49
+
50
+ .nativeListContent
51
+ padding-top 5u
52
+
53
+ &:part(swipe)
54
+ background-color $swipeZoneColor
55
+
56
+ .suggestion
57
+ cursor pointer
58
+ padding 1u 2u
59
+ flex-direction row
60
+ align-items center
61
+
62
+ +desktop()
63
+ padding 1u
64
+
65
+ .sugText
66
+ color var(--color-text-main)
67
+ font(h5)
68
+
69
+ +desktop()
70
+ font(body2)
71
+
72
+ .backdropStyle
73
+ // Greater value than @startup/ui's Modal has
74
+ z-index 1501
75
+
76
+ .popover
77
+ padding 0 .5u
78
+ max-height $suggestionsPopoverMaxHeight
79
+ min-width 20u
80
+
81
+ .ellipsis
82
+ margin 0 1u
83
+
84
+ .suggestionItem
85
+ padding 1u 0
86
+
87
+ .check
88
+ width 2u
89
+
90
+ .checkIcon
91
+ color var(--color-text-primary)
92
+
93
+ .label
94
+ margin-right 1u
95
+ flex-grow 1
package/index.d.ts ADDED
@@ -0,0 +1,61 @@
1
+ /* eslint-disable */
2
+ // DO NOT MODIFY THIS FILE - IT IS AUTOMATICALLY GENERATED ON COMMITS.
3
+
4
+ import { type ReactNode, type RefObject } from 'react';
5
+ import { type StyleProp, type ViewStyle } from 'react-native';
6
+ import './index.cssx.styl';
7
+ declare const _default: import("react").ComponentType<MultiSelectProps>;
8
+ export default _default;
9
+ export declare const _PropsJsonSchema: {};
10
+ export interface MultiSelectOption {
11
+ label: any;
12
+ value: any;
13
+ }
14
+ export interface MultiSelectProps {
15
+ /** Custom styles for the Popover anchor wrapper */
16
+ style?: StyleProp<ViewStyle>;
17
+ /** Custom styles for the input wrapper */
18
+ inputStyle?: StyleProp<ViewStyle>;
19
+ /** Available options (objects with `{ label, value }` or primitives) @default [] */
20
+ options?: Array<MultiSelectOption | string | number | boolean>;
21
+ /** Selected values @default [] */
22
+ value?: any[];
23
+ /** Placeholder text shown when empty @default 'Select' */
24
+ placeholder?: string;
25
+ /** Disable interactions @default false */
26
+ disabled?: boolean;
27
+ /** Render non-editable value @default false */
28
+ readonly?: boolean;
29
+ /** Maximum number of visible tags (extra tags are collapsed) */
30
+ tagLimit?: number;
31
+ /** Behavior when tags are limited (legacy prop) @default 'hidden' */
32
+ tagLimitVariant?: 'hidden' | 'disabled';
33
+ /** Maximum number of selectable tags */
34
+ maxTagCount?: number;
35
+ /** Custom tag renderer */
36
+ TagComponent?: any;
37
+ /** Custom input renderer */
38
+ InputComponent?: any;
39
+ /** Match Popover width to anchor on web @default false */
40
+ hasWidthCaption?: boolean;
41
+ /** Custom suggestion item renderer */
42
+ renderListItem?: (options: {
43
+ item: MultiSelectOption;
44
+ index: number;
45
+ selected: boolean;
46
+ }) => ReactNode;
47
+ /** Called when selected values change */
48
+ onChange?: (value: any[]) => void;
49
+ /** Called when a value is selected */
50
+ onSelect?: (value: any) => void;
51
+ /** Called when a value is removed */
52
+ onRemove?: (value: any) => void;
53
+ /** Called when dropdown opens */
54
+ onFocus?: () => void;
55
+ /** Called when dropdown closes */
56
+ onBlur?: () => void;
57
+ /** Ref providing imperative `focus()` / `blur()` methods */
58
+ ref?: RefObject<any>;
59
+ /** Error flag @private */
60
+ _hasError?: boolean;
61
+ }
package/index.tsx ADDED
@@ -0,0 +1,394 @@
1
+ import {
2
+ useCallback,
3
+ useEffect,
4
+ useImperativeHandle,
5
+ useMemo,
6
+ useState,
7
+ type ReactNode,
8
+ type RefObject
9
+ } from 'react'
10
+ import { Platform, type StyleProp, type ViewStyle } from 'react-native'
11
+ import { pug, observer, useDidUpdate } from 'startupjs'
12
+ import { themed } from '@startupjs-ui/core'
13
+ import Div from '@startupjs-ui/div'
14
+ import Drawer from '@startupjs-ui/drawer'
15
+ import Icon from '@startupjs-ui/icon'
16
+ import Popover from '@startupjs-ui/popover'
17
+ import ScrollView from '@startupjs-ui/scroll-view'
18
+ import Span from '@startupjs-ui/span'
19
+ import Tag from '@startupjs-ui/tag'
20
+ import { faCheck } from '@fortawesome/free-solid-svg-icons/faCheck'
21
+ import './index.cssx.styl'
22
+
23
+ const IS_WEB = Platform.OS === 'web'
24
+
25
+ export default observer(themed('MultiSelect', MultiSelect))
26
+
27
+ export const _PropsJsonSchema = {/* MultiSelectProps */} // used in docs generation
28
+
29
+ export interface MultiSelectOption {
30
+ label: any
31
+ value: any
32
+ }
33
+
34
+ export interface MultiSelectProps {
35
+ /** Custom styles for the Popover anchor wrapper */
36
+ style?: StyleProp<ViewStyle>
37
+ /** Custom styles for the input wrapper */
38
+ inputStyle?: StyleProp<ViewStyle>
39
+ /** Available options (objects with `{ label, value }` or primitives) @default [] */
40
+ options?: Array<MultiSelectOption | string | number | boolean>
41
+ /** Selected values @default [] */
42
+ value?: any[]
43
+ /** Placeholder text shown when empty @default 'Select' */
44
+ placeholder?: string
45
+ /** Disable interactions @default false */
46
+ disabled?: boolean
47
+ /** Render non-editable value @default false */
48
+ readonly?: boolean
49
+ /** Maximum number of visible tags (extra tags are collapsed) */
50
+ tagLimit?: number
51
+ /** Behavior when tags are limited (legacy prop) @default 'hidden' */
52
+ tagLimitVariant?: 'hidden' | 'disabled'
53
+ /** Maximum number of selectable tags */
54
+ maxTagCount?: number
55
+ /** Custom tag renderer */
56
+ TagComponent?: any
57
+ /** Custom input renderer */
58
+ InputComponent?: any
59
+ /** Match Popover width to anchor on web @default false */
60
+ hasWidthCaption?: boolean
61
+ /** Custom suggestion item renderer */
62
+ renderListItem?: (options: { item: MultiSelectOption, index: number, selected: boolean }) => ReactNode
63
+ /** Called when selected values change */
64
+ onChange?: (value: any[]) => void
65
+ /** Called when a value is selected */
66
+ onSelect?: (value: any) => void
67
+ /** Called when a value is removed */
68
+ onRemove?: (value: any) => void
69
+ /** Called when dropdown opens */
70
+ onFocus?: () => void
71
+ /** Called when dropdown closes */
72
+ onBlur?: () => void
73
+ /** Ref providing imperative `focus()` / `blur()` methods */
74
+ ref?: RefObject<any>
75
+ /** Error flag @private */
76
+ _hasError?: boolean
77
+ }
78
+
79
+ function MultiSelect ({
80
+ style,
81
+ inputStyle,
82
+ options = [],
83
+ value = [],
84
+ placeholder = 'Select',
85
+ disabled = false,
86
+ readonly = false,
87
+ tagLimitVariant = 'hidden',
88
+ TagComponent = DefaultTag,
89
+ InputComponent,
90
+ tagLimit,
91
+ maxTagCount,
92
+ hasWidthCaption = false,
93
+ renderListItem,
94
+ onChange,
95
+ onSelect,
96
+ onRemove,
97
+ onFocus,
98
+ onBlur,
99
+ ref,
100
+ _hasError,
101
+ ...props
102
+ }: MultiSelectProps): ReactNode {
103
+ const [focused, setFocused] = useState(false)
104
+ const isOpenable = !(disabled || readonly)
105
+
106
+ const normalizedOptions = useMemo(() => {
107
+ return options.map(opt => typeof opt === 'object' && opt !== null
108
+ ? opt
109
+ : { label: opt, value: opt }
110
+ )
111
+ }, [options])
112
+
113
+ const shouldDisableSelection = maxTagCount
114
+ ? maxTagCount === value.length
115
+ : false
116
+
117
+ const focusHandler = useCallback(() => {
118
+ if (isOpenable) setFocused(true)
119
+ }, [isOpenable])
120
+
121
+ const blurHandler = useCallback(() => { setFocused(false) }, [])
122
+
123
+ const handleChangeVisible = (nextVisible: boolean) => {
124
+ if (!nextVisible) {
125
+ setFocused(false)
126
+ return
127
+ }
128
+ if (!isOpenable) return
129
+ setFocused(true)
130
+ }
131
+
132
+ useDidUpdate(() => {
133
+ if (focused) onFocus && onFocus()
134
+ else onBlur && onBlur()
135
+ }, [focused])
136
+
137
+ useImperativeHandle(ref, () => ({
138
+ focus: focusHandler,
139
+ blur: blurHandler
140
+ }), [focusHandler, blurHandler])
141
+
142
+ useDidUpdate(() => {
143
+ if (focused && !isOpenable) blurHandler()
144
+ }, [focused, isOpenable])
145
+
146
+ function _onRemove (_value: any) {
147
+ onRemove && onRemove(_value)
148
+ onChange && onChange(value.filter(v => v !== _value))
149
+ }
150
+
151
+ function _onSelect (_value: any) {
152
+ onSelect && onSelect(_value)
153
+ onChange && onChange([...value, _value])
154
+ }
155
+
156
+ const onItemPress = (itemValue: any, selected: boolean) => (checked: boolean) => {
157
+ if (disabled || readonly) return
158
+ if (shouldDisableSelection && checked && !selected) return
159
+ if (!checked) {
160
+ _onRemove(itemValue)
161
+ } else {
162
+ _onSelect(itemValue)
163
+ }
164
+ }
165
+
166
+ function _renderListItem ({ item, index }: { item: MultiSelectOption, index: number }): ReactNode {
167
+ const { label, value: itemValue } = item
168
+ const selected = value.includes(itemValue)
169
+ const onPress = onItemPress(itemValue, selected)
170
+
171
+ return pug`
172
+ Div(
173
+ key=itemValue
174
+ vAlign='center'
175
+ disabled=selected ? false : shouldDisableSelection
176
+ onPress=() => onPress(!selected)
177
+ )
178
+ if renderListItem
179
+ = renderListItem({ item, index, selected })
180
+ else
181
+ Div.suggestionItem(row)
182
+ Span.label= label
183
+ Div.check
184
+ if selected
185
+ Icon(icon=faCheck styleName='checkIcon')
186
+ `
187
+ }
188
+
189
+ function renderContent (): ReactNode {
190
+ return pug`
191
+ ScrollView.suggestions-web
192
+ each option, index in normalizedOptions
193
+ = _renderListItem({ item: option, index })
194
+ `
195
+ }
196
+
197
+ return IS_WEB
198
+ ? pug`
199
+ Popover.popover(
200
+ part='root'
201
+ ...props
202
+ captionStyle=style
203
+ visible=focused
204
+ matchAnchorWidth=hasWidthCaption
205
+ attachment='start'
206
+ position='bottom'
207
+ onChange=handleChangeVisible
208
+ renderContent=renderContent
209
+ )
210
+ MultiSelectInput(
211
+ part='input'
212
+ style=inputStyle
213
+ focused=focused
214
+ value=value
215
+ placeholder=placeholder
216
+ tagLimit=tagLimit
217
+ tagLimitVariant=tagLimitVariant
218
+ options=normalizedOptions
219
+ disabled=disabled
220
+ readonly=readonly
221
+ InputComponent=InputComponent
222
+ TagComponent=TagComponent
223
+ _hasError=_hasError
224
+ onOpen=focusHandler
225
+ onHide=blurHandler
226
+ )
227
+ `
228
+ : pug`
229
+ MultiSelectInput(
230
+ part='input'
231
+ style=inputStyle
232
+ onOpen=focusHandler
233
+ onHide=blurHandler
234
+ focused=focused
235
+ value=value
236
+ placeholder=placeholder
237
+ tagLimit=tagLimit
238
+ tagLimitVariant=tagLimitVariant
239
+ options=normalizedOptions
240
+ disabled=disabled
241
+ readonly=readonly
242
+ InputComponent=InputComponent
243
+ TagComponent=TagComponent
244
+ _hasError=_hasError
245
+ )
246
+ Drawer.nativeListContent(
247
+ part='root'
248
+ visible=focused
249
+ position='bottom'
250
+ onDismiss=blurHandler
251
+ )
252
+ ScrollView.suggestions-native
253
+ each option, index in normalizedOptions
254
+ = _renderListItem({ item: option, index })
255
+ `
256
+ }
257
+
258
+ function MultiSelectInput ({
259
+ style,
260
+ value,
261
+ placeholder,
262
+ options,
263
+ disabled,
264
+ readonly,
265
+ focused,
266
+ tagLimit,
267
+ tagLimitVariant,
268
+ TagComponent,
269
+ InputComponent,
270
+ onOpen,
271
+ onHide,
272
+ _hasError
273
+ }: {
274
+ style?: StyleProp<ViewStyle>
275
+ value: any[]
276
+ placeholder?: string
277
+ options: MultiSelectOption[]
278
+ disabled?: boolean
279
+ readonly?: boolean
280
+ focused?: boolean
281
+ tagLimit?: number
282
+ tagLimitVariant?: 'hidden' | 'disabled'
283
+ TagComponent?: any
284
+ InputComponent?: any
285
+ onOpen?: () => void
286
+ onHide?: () => void
287
+ _hasError?: boolean
288
+ }): ReactNode {
289
+ const values = tagLimit ? value.slice(0, tagLimit) : value
290
+ const hiddenTagsLength = tagLimit
291
+ ? value.slice(tagLimit, value.length).length
292
+ : 0
293
+
294
+ const EffectiveInputComponent = InputComponent ?? DefaultInput
295
+
296
+ return pug`
297
+ EffectiveInputComponent(
298
+ part='root'
299
+ style=style
300
+ value=values
301
+ placeholder=placeholder
302
+ disabled=disabled
303
+ focused=focused
304
+ readonly=readonly
305
+ onOpen=onOpen
306
+ onHide=onHide
307
+ _hasError=_hasError
308
+ )
309
+ each value, index in values
310
+ - const record = options.find(r => r.value === value) || {}
311
+ - const isLast = index + 1 === values.length
312
+ TagComponent(
313
+ key=value
314
+ index=index
315
+ isLast=isLast
316
+ record=record
317
+ )
318
+ if hiddenTagsLength
319
+ Span.ellipsis ...
320
+ DefaultTag(
321
+ index=0
322
+ record={ label: '+' + hiddenTagsLength }
323
+ )
324
+ `
325
+ }
326
+
327
+ function DefaultInput ({
328
+ style,
329
+ value = [],
330
+ placeholder,
331
+ disabled,
332
+ focused,
333
+ readonly,
334
+ children,
335
+ onOpen,
336
+ onHide,
337
+ _hasError,
338
+ ref
339
+ }: {
340
+ style?: StyleProp<ViewStyle>
341
+ value?: any[]
342
+ placeholder?: string
343
+ disabled?: boolean
344
+ focused?: boolean
345
+ readonly?: boolean
346
+ children?: ReactNode
347
+ onOpen?: () => void
348
+ onHide?: () => void
349
+ _hasError?: boolean
350
+ ref?: RefObject<any>
351
+ }): ReactNode {
352
+ useImperativeHandle(ref, () => ({
353
+ focus: () => { onOpen && onOpen() },
354
+ blur: () => { onHide && onHide() }
355
+ }), [onOpen, onHide])
356
+
357
+ useEffect(() => {
358
+ if (focused && disabled) onHide && onHide()
359
+ }, [disabled, focused, onHide])
360
+
361
+ return pug`
362
+ if readonly
363
+ Span= value.join(', ')
364
+ else
365
+ Div.input(
366
+ style=style
367
+ styleName={ disabled, focused, readonly, error: _hasError }
368
+ onPress=disabled || readonly ? void 0 : onOpen
369
+ wrap
370
+ row
371
+ )
372
+ if !value.length
373
+ Span.placeholder= placeholder || '-'
374
+
375
+ = children
376
+ `
377
+ }
378
+
379
+ function DefaultTag ({
380
+ record,
381
+ isLast
382
+ }: {
383
+ record?: any
384
+ isLast?: boolean
385
+ }): ReactNode {
386
+ return pug`
387
+ Tag.tag(
388
+ styleName={ last: isLast }
389
+ size='s'
390
+ variant='flat'
391
+ color='primary'
392
+ )= record?.label
393
+ `
394
+ }
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@startupjs-ui/multi-select",
3
+ "version": "0.1.3",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "main": "index.tsx",
8
+ "types": "index.d.ts",
9
+ "type": "module",
10
+ "dependencies": {
11
+ "@startupjs-ui/core": "^0.1.3",
12
+ "@startupjs-ui/div": "^0.1.3",
13
+ "@startupjs-ui/drawer": "^0.1.3",
14
+ "@startupjs-ui/icon": "^0.1.3",
15
+ "@startupjs-ui/popover": "^0.1.3",
16
+ "@startupjs-ui/scroll-view": "^0.1.3",
17
+ "@startupjs-ui/span": "^0.1.3",
18
+ "@startupjs-ui/tag": "^0.1.3"
19
+ },
20
+ "peerDependencies": {
21
+ "react": "*",
22
+ "react-native": "*",
23
+ "startupjs": "*"
24
+ },
25
+ "gitHead": "fd964ebc3892d3dd0a6c85438c0af619cc50c3f0"
26
+ }