nitro-web 0.0.193 → 0.0.195
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.
|
@@ -7,7 +7,7 @@ import ReactSelect, {
|
|
|
7
7
|
ValueContainerProps,
|
|
8
8
|
} from 'react-select'
|
|
9
9
|
import { CheckCircleIcon } from '@heroicons/react/20/solid'
|
|
10
|
-
import { ChevronsUpDownIcon, XIcon } from 'lucide-react'
|
|
10
|
+
import { ChevronsUpDownIcon, SearchIcon, XIcon } from 'lucide-react'
|
|
11
11
|
import { isFieldCached } from 'nitro-web'
|
|
12
12
|
import { getErrorFromState, deepFind, twMerge } from 'nitro-web/util'
|
|
13
13
|
import { Errors } from 'nitro-web/types'
|
|
@@ -22,6 +22,7 @@ type GetSelectClassName = {
|
|
|
22
22
|
isDisabled?: boolean
|
|
23
23
|
hasError?: boolean
|
|
24
24
|
usePrefixes?: boolean
|
|
25
|
+
classNames?: ClassNames
|
|
25
26
|
}
|
|
26
27
|
export type SelectOption = {
|
|
27
28
|
value: unknown,
|
|
@@ -58,7 +59,9 @@ export type SelectProps = {
|
|
|
58
59
|
/** title used to find related error messages */
|
|
59
60
|
errorTitle?: string|RegExp
|
|
60
61
|
/** Extend or override individual react-select part class names — merged with defaults via twMerge **/
|
|
61
|
-
classNames?:
|
|
62
|
+
classNames?: ClassNames
|
|
63
|
+
/** Show a search icon instead of the dropdown arrow **/
|
|
64
|
+
showSearchIcon?: boolean
|
|
62
65
|
/** All other props are passed to react-select **/
|
|
63
66
|
[key: string]: unknown
|
|
64
67
|
}
|
|
@@ -67,8 +70,10 @@ export const Select = memo(SelectBase, (prev, next) => {
|
|
|
67
70
|
return isFieldCached(prev, next)
|
|
68
71
|
})
|
|
69
72
|
|
|
70
|
-
function SelectBase({
|
|
71
|
-
id, containerId, minMenuWidth, name, prefix='', onChange, options, state, mode='', errorTitle, classNames,
|
|
73
|
+
function SelectBase({
|
|
74
|
+
id, containerId, minMenuWidth, name, prefix='', onChange, options, state, mode='', errorTitle, classNames: classNamesProp,
|
|
75
|
+
showSearchIcon, className,
|
|
76
|
+
...props
|
|
72
77
|
}: SelectProps) {
|
|
73
78
|
let value: unknown|unknown[]
|
|
74
79
|
const error = getErrorFromState(state, errorTitle || name)
|
|
@@ -85,33 +90,29 @@ function SelectBase({
|
|
|
85
90
|
// Input is always controlled if state is passed in
|
|
86
91
|
if (typeof state == 'object' && typeof value == 'undefined') value = ''
|
|
87
92
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
option: (p) => getSelectClassName({ name: 'option', ...p }),
|
|
109
|
-
// Nitro specific
|
|
110
|
-
flag: () => getSelectClassName({ name: 'flag' }),
|
|
111
|
-
}, classNames), [!!error, classNames])
|
|
93
|
+
// Merge class names (up to 1 level deep)
|
|
94
|
+
const classNames = useMemo(() => {
|
|
95
|
+
const merged = { ...selectClassNames }
|
|
96
|
+
for (const key in classNamesProp) {
|
|
97
|
+
const value = classNamesProp[key as keyof ClassNames]
|
|
98
|
+
if (typeof value == 'object') {
|
|
99
|
+
// @ts-expect-error
|
|
100
|
+
merged[key] = { ...merged[key] }
|
|
101
|
+
for (const key2 in value) {
|
|
102
|
+
const value2 = value[key2 as keyof typeof value]
|
|
103
|
+
// @ts-expect-error
|
|
104
|
+
merged[key][key2] = twMerge(merged[key][key2], value2)
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
// @ts-expect-error
|
|
108
|
+
merged[key] = twMerge(merged[key], value)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return merged
|
|
112
|
+
}, [classNamesProp])
|
|
112
113
|
|
|
113
114
|
return (
|
|
114
|
-
<div css={style} class={'mt-2.5 mb-6 ' + twMerge(`mt-input-before mb-input-after nitro-select ${
|
|
115
|
+
<div css={style} class={'mt-2.5 mb-6 ' + twMerge(`mt-input-before mb-input-after nitro-select ${className || ''}`)}>
|
|
115
116
|
<ReactSelect
|
|
116
117
|
/**
|
|
117
118
|
* react-select prop quick reference (https://react-select.com/props#api):
|
|
@@ -126,7 +127,7 @@ function SelectBase({
|
|
|
126
127
|
*/
|
|
127
128
|
{...props}
|
|
128
129
|
// @ts-expect-error
|
|
129
|
-
_nitro={{ prefix, mode }}
|
|
130
|
+
_nitro={{ prefix, mode, showSearchIcon }}
|
|
130
131
|
key={value as string}
|
|
131
132
|
unstyled={true}
|
|
132
133
|
inputId={id || name}
|
|
@@ -147,15 +148,40 @@ function SelectBase({
|
|
|
147
148
|
}}
|
|
148
149
|
options={options}
|
|
149
150
|
value={value}
|
|
150
|
-
classNames={
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
classNames={useMemo(() => ({
|
|
152
|
+
// Input container
|
|
153
|
+
control: (p) => getSelectClassName({ name: 'control', hasError: !!error, ...p, classNames: classNames }),
|
|
154
|
+
valueContainer: () => getSelectClassName({ name: 'valueContainer', classNames: classNames }),
|
|
155
|
+
// Input container objects
|
|
156
|
+
input: () => getSelectClassName({ name: 'input', hasError: !!error, classNames: classNames }),
|
|
157
|
+
multiValue: () => getSelectClassName({ name: 'multiValue', classNames: classNames }),
|
|
158
|
+
multiValueLabel: () => '',
|
|
159
|
+
multiValueRemove: () => getSelectClassName({ name: 'multiValueRemove', classNames: classNames }),
|
|
160
|
+
placeholder: () => getSelectClassName({ name: 'placeholder', classNames: classNames }),
|
|
161
|
+
singleValue: (p) => getSelectClassName({ name: 'singleValue', hasError: !!error, isDisabled: p.isDisabled,
|
|
162
|
+
classNames: classNames }),
|
|
163
|
+
// Indicators
|
|
164
|
+
clearIndicator: () => getSelectClassName({ name: 'clearIndicator', classNames: classNames }),
|
|
165
|
+
dropdownIndicator: () => getSelectClassName({ name: 'dropdownIndicator', classNames: classNames }),
|
|
166
|
+
indicatorsContainer: () => getSelectClassName({ name: 'indicatorsContainer', classNames: classNames }),
|
|
167
|
+
indicatorSeparator: () => getSelectClassName({ name: 'indicatorSeparator', classNames: classNames }),
|
|
168
|
+
// Dropmenu
|
|
169
|
+
menu: () => getSelectClassName({ name: 'menu', classNames: classNames }),
|
|
170
|
+
groupHeading: () => getSelectClassName({ name: 'groupHeading', classNames: classNames }),
|
|
171
|
+
noOptionsMessage: () => getSelectClassName({ name: 'noOptionsMessage', classNames: classNames }),
|
|
172
|
+
option: (p) => getSelectClassName({ name: 'option', ...p, classNames: classNames }),
|
|
173
|
+
// Nitro specific
|
|
174
|
+
flag: () => getSelectClassName({ name: 'flag', classNames: classNames }),
|
|
175
|
+
}), [!!error, classNames])}
|
|
176
|
+
components={{
|
|
177
|
+
Control,
|
|
178
|
+
SingleValue,
|
|
154
179
|
Option,
|
|
155
|
-
DropdownIndicator,
|
|
156
|
-
ClearIndicator,
|
|
180
|
+
DropdownIndicator,
|
|
181
|
+
ClearIndicator,
|
|
157
182
|
MultiValueRemove,
|
|
158
183
|
ValueContainer,
|
|
184
|
+
...props.components as object,
|
|
159
185
|
}}
|
|
160
186
|
styles={{
|
|
161
187
|
menu: (base) => ({
|
|
@@ -245,9 +271,13 @@ function Option(props: OptionProps) {
|
|
|
245
271
|
}
|
|
246
272
|
|
|
247
273
|
const DropdownIndicator = (props: DropdownIndicatorProps) => {
|
|
274
|
+
const _nitro = (props.selectProps as { _nitro?: { showSearchIcon?: boolean } })?._nitro
|
|
275
|
+
const isSearch = _nitro?.showSearchIcon
|
|
276
|
+
const Icon = isSearch ? SearchIcon : ChevronsUpDownIcon
|
|
248
277
|
return (
|
|
249
278
|
<components.DropdownIndicator {...props}>
|
|
250
|
-
<
|
|
279
|
+
<Icon size={isSearch ? 13 : 15} strokeWidth={isSearch ? 2.4 : undefined}
|
|
280
|
+
className="text-[#b6b8be] text-input-icon -my-0.5 -mx-[1px]" />
|
|
251
281
|
</components.DropdownIndicator>
|
|
252
282
|
)
|
|
253
283
|
}
|
|
@@ -309,12 +339,36 @@ const selectClassNames = {
|
|
|
309
339
|
},
|
|
310
340
|
// Nitro specific
|
|
311
341
|
flag: 'align-middle text-[1.2em] leading-[1em] mr-1.5 flex-shrink-0',
|
|
342
|
+
} as const
|
|
343
|
+
|
|
344
|
+
type ClassNames = Partial<Omit<typeof selectClassNames, 'control' | 'input' | 'singleValue' | 'option'>> & {
|
|
345
|
+
control?: {
|
|
346
|
+
base?: string
|
|
347
|
+
focus?: string
|
|
348
|
+
error?: string
|
|
349
|
+
disabled?: string
|
|
350
|
+
}
|
|
351
|
+
input?: {
|
|
352
|
+
base?: string
|
|
353
|
+
error?: string
|
|
354
|
+
disabled?: string
|
|
355
|
+
}
|
|
356
|
+
singleValue?: {
|
|
357
|
+
base?: string
|
|
358
|
+
error?: string
|
|
359
|
+
disabled?: string
|
|
360
|
+
}
|
|
361
|
+
option?: {
|
|
362
|
+
base?: string
|
|
363
|
+
hover?: string
|
|
364
|
+
selected?: string
|
|
365
|
+
}
|
|
312
366
|
}
|
|
313
367
|
|
|
314
|
-
export function getSelectClassName({ name, isFocused, isSelected, isDisabled, hasError, usePrefixes }: GetSelectClassName) {
|
|
368
|
+
export function getSelectClassName({ name, isFocused, isSelected, isDisabled, hasError, usePrefixes, classNames }: GetSelectClassName) {
|
|
315
369
|
// Returns a class list that conditionally includes hover/focus modifier classes, or uses CSS modifiers, e.g. hover:, focus:
|
|
316
370
|
// @ts-expect-error
|
|
317
|
-
const obj = selectClassNames[name]
|
|
371
|
+
const obj = classNames?.[name] || selectClassNames[name]
|
|
318
372
|
let output = obj?.base
|
|
319
373
|
if (typeof obj == 'string') return obj // no modifiers
|
|
320
374
|
|
|
@@ -332,11 +386,6 @@ export function getSelectClassName({ name, isFocused, isSelected, isDisabled, ha
|
|
|
332
386
|
return twMerge(output)
|
|
333
387
|
}
|
|
334
388
|
|
|
335
|
-
function mergeClassNames(defaults: NitroClassNamesConfig, custom?: NitroClassNamesConfig): NitroClassNamesConfig {
|
|
336
|
-
if (!custom) return defaults
|
|
337
|
-
return { ...defaults, ...custom }
|
|
338
|
-
}
|
|
339
|
-
|
|
340
389
|
const style = css`
|
|
341
390
|
/*
|
|
342
391
|
todo: add these as tailwind classes
|
|
@@ -487,23 +487,23 @@ export function Styleguide({ className, elements, children, currencies, groups }
|
|
|
487
487
|
{
|
|
488
488
|
value: '1',
|
|
489
489
|
label: 'Wayne Enterprises',
|
|
490
|
-
IconLeft: <Initials initials="WE" className="inline-flex my-[-
|
|
490
|
+
IconLeft: <Initials initials="WE" className="inline-flex my-[-2px] mr-2 flex-shrink-0" />,
|
|
491
491
|
},
|
|
492
492
|
{
|
|
493
493
|
value: '2',
|
|
494
494
|
label: 'Iceberg Lounge Limited',
|
|
495
|
-
IconLeft: <Initials initials="IL" className="inline-flex my-[-
|
|
495
|
+
IconLeft: <Initials initials="IL" className="inline-flex my-[-2px] mr-2 flex-shrink-0" />,
|
|
496
496
|
},
|
|
497
497
|
{
|
|
498
498
|
value: '3',
|
|
499
499
|
label: 'Ace Chemicals Company',
|
|
500
|
-
IconLeft: <Initials initials="AC" className="inline-flex my-[-
|
|
500
|
+
IconLeft: <Initials initials="AC" className="inline-flex my-[-2px] mr-2 flex-shrink-0" />,
|
|
501
501
|
},
|
|
502
502
|
], [customerSearch])}
|
|
503
503
|
/>
|
|
504
504
|
</div>
|
|
505
505
|
<div>
|
|
506
|
-
<label for="currency">Currencies</label>
|
|
506
|
+
<label for="currency">Currencies (and classNames prop)</label>
|
|
507
507
|
<Select
|
|
508
508
|
name="currency"
|
|
509
509
|
state={state}
|
|
@@ -512,6 +512,11 @@ export function Styleguide({ className, elements, children, currencies, groups }
|
|
|
512
512
|
{ value: 'aud', label: 'Australian Dollar' },
|
|
513
513
|
]), [])}
|
|
514
514
|
onChange={(e) => onChange(e, setState)}
|
|
515
|
+
classNames={{
|
|
516
|
+
control: {
|
|
517
|
+
focus: 'outline-secondary',
|
|
518
|
+
},
|
|
519
|
+
}}
|
|
515
520
|
/>
|
|
516
521
|
</div>
|
|
517
522
|
<div>
|
|
@@ -522,7 +527,7 @@ export function Styleguide({ className, elements, children, currencies, groups }
|
|
|
522
527
|
options={useMemo(() => [
|
|
523
528
|
{ value: 'edit', label: 'Edit' },
|
|
524
529
|
{ value: 'delete', label: 'Delete' },
|
|
525
|
-
], [])}
|
|
530
|
+
], [])}
|
|
526
531
|
/>
|
|
527
532
|
</div>
|
|
528
533
|
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nitro-web",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.195",
|
|
4
4
|
"repository": "github:boycce/nitro-web",
|
|
5
5
|
"homepage": "https://boycce.github.io/nitro-web/",
|
|
6
6
|
"description": "Nitro is a battle-tested, modular base project to turbocharge your projects, styled using Tailwind 🚀",
|