nitro-web 0.0.194 → 0.0.196
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.
|
@@ -29,6 +29,8 @@ type FieldExtraProps = {
|
|
|
29
29
|
placeholder?: string
|
|
30
30
|
/** title used to find related error messages */
|
|
31
31
|
errorTitle?: string|RegExp
|
|
32
|
+
/** class names to override the default class names */
|
|
33
|
+
inputClassName?: string
|
|
32
34
|
}
|
|
33
35
|
type IconWrapperProps = {
|
|
34
36
|
iconPos: 'left' | 'right'
|
|
@@ -54,7 +56,7 @@ export const Field = memo(FieldBase, (prev, next) => {
|
|
|
54
56
|
return isFieldCached(prev, next)
|
|
55
57
|
})
|
|
56
58
|
|
|
57
|
-
function FieldBase({ state, icon, iconPos: ip, errorTitle, ...props }: FieldProps) {
|
|
59
|
+
function FieldBase({ state, icon, iconPos: ip, errorTitle, inputClassName, ...props }: FieldProps) {
|
|
58
60
|
// `type` must be kept as props.type for TS to be happy and follow the conditions below
|
|
59
61
|
let value!: any
|
|
60
62
|
let Icon!: React.ReactNode
|
|
@@ -104,8 +106,9 @@ function FieldBase({ state, icon, iconPos: ip, errorTitle, ...props }: FieldProp
|
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
// Classname
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
+
const inputClasses = getInputClasses({ error, Icon, iconPos, type })
|
|
110
|
+
const inputClassName2 = inputClassName ? twMerge(inputClasses, inputClassName) : inputClasses.replaceAll(/\(|\)/g, '')
|
|
111
|
+
const commonProps = { id: id, value: value, className: inputClassName2 }
|
|
109
112
|
|
|
110
113
|
// Type has to be referenced as props.type for TS to be happy
|
|
111
114
|
if (!type || type == 'text' || type == 'number' || type == 'password' || type == 'email' || type == 'filter' || type == 'search') {
|
|
@@ -160,9 +163,11 @@ function getInputClasses({ error, Icon, iconPos, type }: { error?: Error, Icon?:
|
|
|
160
163
|
const py = 'py-[9px] py-input-y'
|
|
161
164
|
return (
|
|
162
165
|
'block col-start-1 row-start-1 w-full rounded-md bg-white disabled:bg-input-disabled-bg text-input-base outline outline-1 -outline-offset-1 ' +
|
|
163
|
-
'placeholder:text-input-placeholder focus:outline focus:outline-2 focus:-outline-offset-2 ' +
|
|
164
|
-
|
|
165
|
-
|
|
166
|
+
'placeholder:text-input-placeholder focus:outline focus:outline-2 focus:-outline-offset-2 (' +
|
|
167
|
+
`${py} ${px} ` +
|
|
168
|
+
(iconPos == 'right' && Icon ? 'pr-[32px] pr-input-x-icon pl-input-x ' : '') +
|
|
169
|
+
(iconPos == 'left' && Icon ? 'pl-[32px] pl-input-x-icon pr-input-x ' : 'px-input-x ') +
|
|
170
|
+
')' +
|
|
166
171
|
(iconPos == 'left' && Icon && type == 'color' ? 'indent-[5px] ' : '') +
|
|
167
172
|
(error
|
|
168
173
|
? 'text-danger-foreground outline-danger focus:outline-danger '
|
|
@@ -3,18 +3,17 @@ import { css } from 'twin.macro'
|
|
|
3
3
|
import { memo, useMemo, Fragment } from 'react'
|
|
4
4
|
import ReactSelect, {
|
|
5
5
|
components, ControlProps, createFilter, OptionProps, SingleValueProps, ClearIndicatorProps,
|
|
6
|
-
DropdownIndicatorProps, MultiValueRemoveProps, ClassNamesConfig,
|
|
6
|
+
DropdownIndicatorProps, MultiValueRemoveProps, // ClassNamesConfig,
|
|
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'
|
|
14
14
|
|
|
15
15
|
const filterFn = createFilter()
|
|
16
16
|
|
|
17
|
-
type NitroClassNamesConfig = ClassNamesConfig & { flag?: () => string }
|
|
18
17
|
type GetSelectClassName = {
|
|
19
18
|
name: string
|
|
20
19
|
isFocused?: boolean
|
|
@@ -22,6 +21,7 @@ type GetSelectClassName = {
|
|
|
22
21
|
isDisabled?: boolean
|
|
23
22
|
hasError?: boolean
|
|
24
23
|
usePrefixes?: boolean
|
|
24
|
+
classNames?: ClassNames
|
|
25
25
|
}
|
|
26
26
|
export type SelectOption = {
|
|
27
27
|
value: unknown,
|
|
@@ -58,7 +58,9 @@ export type SelectProps = {
|
|
|
58
58
|
/** title used to find related error messages */
|
|
59
59
|
errorTitle?: string|RegExp
|
|
60
60
|
/** Extend or override individual react-select part class names — merged with defaults via twMerge **/
|
|
61
|
-
classNames?:
|
|
61
|
+
classNames?: ClassNames
|
|
62
|
+
/** Show a search icon instead of the dropdown arrow **/
|
|
63
|
+
showSearchIcon?: boolean
|
|
62
64
|
/** All other props are passed to react-select **/
|
|
63
65
|
[key: string]: unknown
|
|
64
66
|
}
|
|
@@ -67,8 +69,10 @@ export const Select = memo(SelectBase, (prev, next) => {
|
|
|
67
69
|
return isFieldCached(prev, next)
|
|
68
70
|
})
|
|
69
71
|
|
|
70
|
-
function SelectBase({
|
|
71
|
-
id, containerId, minMenuWidth, name, prefix='', onChange, options, state, mode='', errorTitle, classNames,
|
|
72
|
+
function SelectBase({
|
|
73
|
+
id, containerId, minMenuWidth, name, prefix='', onChange, options, state, mode='', errorTitle, classNames: classNamesProp,
|
|
74
|
+
showSearchIcon, className,
|
|
75
|
+
...props
|
|
72
76
|
}: SelectProps) {
|
|
73
77
|
let value: unknown|unknown[]
|
|
74
78
|
const error = getErrorFromState(state, errorTitle || name)
|
|
@@ -85,33 +89,29 @@ function SelectBase({
|
|
|
85
89
|
// Input is always controlled if state is passed in
|
|
86
90
|
if (typeof state == 'object' && typeof value == 'undefined') value = ''
|
|
87
91
|
|
|
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])
|
|
92
|
+
// Merge class names (up to 1 level deep)
|
|
93
|
+
const classNames = useMemo(() => {
|
|
94
|
+
const merged = { ...selectClassNames }
|
|
95
|
+
for (const key in classNamesProp) {
|
|
96
|
+
const value = classNamesProp[key as keyof ClassNames]
|
|
97
|
+
if (typeof value == 'object') {
|
|
98
|
+
// @ts-expect-error
|
|
99
|
+
merged[key] = { ...merged[key] }
|
|
100
|
+
for (const key2 in value) {
|
|
101
|
+
const value2 = value[key2 as keyof typeof value]
|
|
102
|
+
// @ts-expect-error
|
|
103
|
+
merged[key][key2] = twMerge(merged[key][key2], value2)
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
// @ts-expect-error
|
|
107
|
+
merged[key] = twMerge(merged[key], value)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return merged
|
|
111
|
+
}, [classNamesProp])
|
|
112
112
|
|
|
113
113
|
return (
|
|
114
|
-
<div css={style} class={'mt-2.5 mb-6 ' + twMerge(`mt-input-before mb-input-after nitro-select ${
|
|
114
|
+
<div css={style} class={'mt-2.5 mb-6 ' + twMerge(`mt-input-before mb-input-after nitro-select ${className || ''}`)}>
|
|
115
115
|
<ReactSelect
|
|
116
116
|
/**
|
|
117
117
|
* react-select prop quick reference (https://react-select.com/props#api):
|
|
@@ -126,7 +126,7 @@ function SelectBase({
|
|
|
126
126
|
*/
|
|
127
127
|
{...props}
|
|
128
128
|
// @ts-expect-error
|
|
129
|
-
_nitro={{ prefix, mode }}
|
|
129
|
+
_nitro={{ prefix, mode, showSearchIcon }}
|
|
130
130
|
key={value as string}
|
|
131
131
|
unstyled={true}
|
|
132
132
|
inputId={id || name}
|
|
@@ -147,15 +147,40 @@ function SelectBase({
|
|
|
147
147
|
}}
|
|
148
148
|
options={options}
|
|
149
149
|
value={value}
|
|
150
|
-
classNames={
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
150
|
+
classNames={useMemo(() => ({
|
|
151
|
+
// Input container
|
|
152
|
+
control: (p) => getSelectClassName({ name: 'control', hasError: !!error, ...p, classNames: classNames }),
|
|
153
|
+
valueContainer: () => getSelectClassName({ name: 'valueContainer', classNames: classNames }),
|
|
154
|
+
// Input container objects
|
|
155
|
+
input: () => getSelectClassName({ name: 'input', hasError: !!error, classNames: classNames }),
|
|
156
|
+
multiValue: () => getSelectClassName({ name: 'multiValue', classNames: classNames }),
|
|
157
|
+
multiValueLabel: () => '',
|
|
158
|
+
multiValueRemove: () => getSelectClassName({ name: 'multiValueRemove', classNames: classNames }),
|
|
159
|
+
placeholder: () => getSelectClassName({ name: 'placeholder', classNames: classNames }),
|
|
160
|
+
singleValue: (p) => getSelectClassName({ name: 'singleValue', hasError: !!error, isDisabled: p.isDisabled,
|
|
161
|
+
classNames: classNames }),
|
|
162
|
+
// Indicators
|
|
163
|
+
clearIndicator: () => getSelectClassName({ name: 'clearIndicator', classNames: classNames }),
|
|
164
|
+
dropdownIndicator: () => getSelectClassName({ name: 'dropdownIndicator', classNames: classNames }),
|
|
165
|
+
indicatorsContainer: () => getSelectClassName({ name: 'indicatorsContainer', classNames: classNames }),
|
|
166
|
+
indicatorSeparator: () => getSelectClassName({ name: 'indicatorSeparator', classNames: classNames }),
|
|
167
|
+
// Dropmenu
|
|
168
|
+
menu: () => getSelectClassName({ name: 'menu', classNames: classNames }),
|
|
169
|
+
groupHeading: () => getSelectClassName({ name: 'groupHeading', classNames: classNames }),
|
|
170
|
+
noOptionsMessage: () => getSelectClassName({ name: 'noOptionsMessage', classNames: classNames }),
|
|
171
|
+
option: (p) => getSelectClassName({ name: 'option', ...p, classNames: classNames }),
|
|
172
|
+
// Nitro specific
|
|
173
|
+
flag: () => getSelectClassName({ name: 'flag', classNames: classNames }),
|
|
174
|
+
}), [!!error, classNames])}
|
|
175
|
+
components={{
|
|
176
|
+
Control,
|
|
177
|
+
SingleValue,
|
|
154
178
|
Option,
|
|
155
|
-
DropdownIndicator,
|
|
156
|
-
ClearIndicator,
|
|
179
|
+
DropdownIndicator,
|
|
180
|
+
ClearIndicator,
|
|
157
181
|
MultiValueRemove,
|
|
158
182
|
ValueContainer,
|
|
183
|
+
...props.components as object,
|
|
159
184
|
}}
|
|
160
185
|
styles={{
|
|
161
186
|
menu: (base) => ({
|
|
@@ -245,9 +270,13 @@ function Option(props: OptionProps) {
|
|
|
245
270
|
}
|
|
246
271
|
|
|
247
272
|
const DropdownIndicator = (props: DropdownIndicatorProps) => {
|
|
273
|
+
const _nitro = (props.selectProps as { _nitro?: { showSearchIcon?: boolean } })?._nitro
|
|
274
|
+
const isSearch = _nitro?.showSearchIcon
|
|
275
|
+
const Icon = isSearch ? SearchIcon : ChevronsUpDownIcon
|
|
248
276
|
return (
|
|
249
277
|
<components.DropdownIndicator {...props}>
|
|
250
|
-
<
|
|
278
|
+
<Icon size={isSearch ? 13 : 15} strokeWidth={isSearch ? 2.4 : undefined}
|
|
279
|
+
className="text-[#b6b8be] text-input-icon -my-0.5 -mx-[1px]" />
|
|
251
280
|
</components.DropdownIndicator>
|
|
252
281
|
)
|
|
253
282
|
}
|
|
@@ -278,7 +307,7 @@ const selectClassNames = {
|
|
|
278
307
|
error: 'outline-danger',
|
|
279
308
|
disabled: 'cursor-not-allowed bg-input-disabled-bg',
|
|
280
309
|
},
|
|
281
|
-
valueContainer: 'gap-1 py-[9px] px-[12px] py-input-y px-input-x', // dont twMerge (input-x is optional)
|
|
310
|
+
valueContainer: 'gap-1 (py-[9px] px-[12px] py-input-y px-input-x)', // dont twMerge (input-x is optional)
|
|
282
311
|
// Input container objects
|
|
283
312
|
input: {
|
|
284
313
|
base: 'text-input',
|
|
@@ -309,14 +338,41 @@ const selectClassNames = {
|
|
|
309
338
|
},
|
|
310
339
|
// Nitro specific
|
|
311
340
|
flag: 'align-middle text-[1.2em] leading-[1em] mr-1.5 flex-shrink-0',
|
|
341
|
+
} as const
|
|
342
|
+
|
|
343
|
+
type ClassNames = {
|
|
344
|
+
// Input container
|
|
345
|
+
control?: { base?: string, focus?: string, error?: string, disabled?: string }
|
|
346
|
+
valueContainer?: string
|
|
347
|
+
// Input container objects
|
|
348
|
+
input?: { base?: string, error?: string, disabled?: string }
|
|
349
|
+
multiValue?: string
|
|
350
|
+
multiValueLabel?: string
|
|
351
|
+
multiValueRemove?: string
|
|
352
|
+
placeholder?: string
|
|
353
|
+
singleValue?: { base?: string, error?: string, disabled?: string }
|
|
354
|
+
// Icon indicators
|
|
355
|
+
clearIndicator?: string
|
|
356
|
+
dropdownIndicator?: string
|
|
357
|
+
indicatorsContainer?: string
|
|
358
|
+
indicatorSeparator?: string
|
|
359
|
+
// Dropdown menu
|
|
360
|
+
menu?: string
|
|
361
|
+
groupHeading?: string
|
|
362
|
+
noOptionsMessage?: string
|
|
363
|
+
option?: { base?: string, hover?: string, selected?: string }
|
|
364
|
+
// Nitro specific
|
|
365
|
+
flag?: string
|
|
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
|
+
|
|
374
|
+
if (typeof obj == 'string' && obj.includes('(')) return obj.replaceAll(/\(|\)/g, '') // no modifiers & still has group wrappers
|
|
375
|
+
else if (typeof obj == 'string') return obj // no modifiers
|
|
320
376
|
|
|
321
377
|
if (usePrefixes) {
|
|
322
378
|
if (obj.focus) output += ' ' + obj.focus.split(' ').map((part: string) => `focus:${part}`).join(' ')
|
|
@@ -332,11 +388,6 @@ export function getSelectClassName({ name, isFocused, isSelected, isDisabled, ha
|
|
|
332
388
|
return twMerge(output)
|
|
333
389
|
}
|
|
334
390
|
|
|
335
|
-
function mergeClassNames(defaults: NitroClassNamesConfig, custom?: NitroClassNamesConfig): NitroClassNamesConfig {
|
|
336
|
-
if (!custom) return defaults
|
|
337
|
-
return { ...defaults, ...custom }
|
|
338
|
-
}
|
|
339
|
-
|
|
340
391
|
const style = css`
|
|
341
392
|
/*
|
|
342
393
|
todo: add these as tailwind classes
|
|
@@ -503,7 +503,7 @@ export function Styleguide({ className, elements, children, currencies, groups }
|
|
|
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.196",
|
|
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 🚀",
|