nitro-web 0.0.49 → 0.0.51

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/client/index.ts CHANGED
@@ -42,7 +42,7 @@ export { Checkbox } from '../components/partials/form/checkbox'
42
42
  export { Drop } from '../components/partials/form/drop'
43
43
  export { DropHandler } from '../components/partials/form/drop-handler'
44
44
  export { FormError } from '../components/partials/form/form-error'
45
- export { Field } from '../components/partials/form/field'
45
+ export { Field, isFieldCached } from '../components/partials/form/field'
46
46
  export { FieldColor, type FieldColorProps } from '../components/partials/form/field-color'
47
47
  export { FieldCurrency, type FieldCurrencyProps } from '../components/partials/form/field-currency'
48
48
  export { FieldDate, type FieldDateProps } from '../components/partials/form/field-date'
@@ -7,7 +7,6 @@ export function Signin() {
7
7
  const isSignout = location.pathname == '/signout'
8
8
  const isLoading = useState(isSignout)
9
9
  const [, setStore] = useTracked()
10
-
11
10
  const [state, setState] = useState({
12
11
  email: injectedConfig.env == 'development' ? (injectedConfig.placeholderEmail || '') : '',
13
12
  password: injectedConfig.env == 'development' ? '1234' : '',
@@ -2,8 +2,9 @@ import { twMerge } from 'nitro-web'
2
2
  import { ChevronDown, ChevronUp } from 'lucide-react'
3
3
 
4
4
  type Button = React.ButtonHTMLAttributes<HTMLButtonElement> & {
5
- color?: 'primary'|'secondary'|'black'|'white'|'clear'
5
+ color?: 'primary'|'secondary'|'black'|'dark'|'white'|'clear'|'custom'
6
6
  size?: 'xs'|'sm'|'md'|'lg'
7
+ customColor?: string
7
8
  className?: string
8
9
  isLoading?: boolean
9
10
  IconLeft?: React.ReactNode|'v'
@@ -13,9 +14,10 @@ type Button = React.ButtonHTMLAttributes<HTMLButtonElement> & {
13
14
  children?: React.ReactNode|'v'
14
15
  }
15
16
 
16
- export function Button({
17
+ export function Button({
17
18
  size='md',
18
19
  color='primary',
20
+ customColor,
19
21
  className,
20
22
  isLoading,
21
23
  IconLeft,
@@ -29,25 +31,27 @@ export function Button({
29
31
  const iconPosition = IconLeft ? 'left' : IconLeftEnd ? 'leftEnd' : IconRight ? 'right' : IconRightEnd ? 'rightEnd' : 'none'
30
32
  const base =
31
33
  'relative inline-block text-center font-medium shadow-sm focus-visible:outline focus-visible:outline-2 ' +
32
- 'focus-visible:outline-offset-2 text-white [&>.loader]:border-white'
34
+ 'focus-visible:outline-offset-2 text-white [&>.loader]:border-white ring-inset ring-1'
33
35
 
34
36
  // Button colors, you can use custom colors by using className instead
35
37
  const colors = {
36
- primary: 'bg-primary hover:bg-primary-hover',
37
- secondary: 'bg-secondary hover:bg-secondary-hover',
38
- black: 'bg-black hover:bg-gray-700',
39
- white: 'bg-white ring-1 ring-inset ring-gray-300 hover:bg-gray-50 text-gray-900 [&>.loader]:border-black',
40
- clear: 'ring-1 ring-inset ring-gray-300 hover:bg-gray-50 text-foreground [&>.loader]:border-foreground !shadow-none',
38
+ 'primary': 'bg-primary hover:bg-primary-hover ring-transparent !ring-button-primary-ring',
39
+ 'secondary': 'bg-secondary hover:bg-secondary-hover ring-transparent !ring-button-secondary-ring',
40
+ 'black': 'bg-black hover:bg-gray-700 ring-transparent !ring-button-black-ring',
41
+ 'dark': 'bg-gray-800 !bg-button-dark hover:bg-gray-700 hover:!bg-button-dark-hover ring-transparent !ring-button-dark-ring',
42
+ 'white': 'bg-white ring-gray-300 !ring-button-white-ring hover:bg-gray-50 text-gray-900 [&>.loader]:border-black',
43
+ 'clear': 'ring-gray-300 !ring-button-clear-ring hover:bg-gray-50 text-foreground [&>.loader]:border-foreground !shadow-none',
41
44
  }
42
45
 
43
- // Button sizes
46
+ // Button sizes (px is better for height consistency)
44
47
  const sizes = {
45
- xs: 'px-2 py-1 px-button-x-xs py-button-y-xs text-xs rounded',
46
- sm: 'px-2.5 py-1.5 px-button-x-sm py-button-y-sm text-sm text-sm-button rounded-md',
47
- md: 'px-3 py-[0.58rem] px-button-x-md py-button-y-md text-sm text-sm-button rounded-md', // default
48
- lg: 'px-3.5 py-2.5 px-button-x-lg py-button-y-lg text-sm text-sm-button rounded-md',
48
+ xs: 'px-[6px] py-[3px] px-button-x-xs py-button-y-xs text-xs rounded',
49
+ sm: 'px-[10px] py-[6px] px-button-x-sm py-button-y-sm text-button-size rounded-md',
50
+ md: 'px-[12px] py-[9px] px-button-x-md py-button-y-md text-button-size rounded-md', // default
51
+ lg: 'px-[18px] py-[11px] px-button-x-lg py-button-y-lg text-button-size rounded-md',
49
52
  }
50
-
53
+
54
+ const appliedColor = color === 'custom' ? customColor : colors[color]
51
55
  const contentLayout = `gap-x-1.5 ${iconPosition == 'none' ? '' : 'inline-flex items-center justify-center'}`
52
56
  const loading = isLoading ? '[&>*]:opacity-0 text-opacity-0' : ''
53
57
 
@@ -58,7 +62,7 @@ export function Button({
58
62
  }
59
63
 
60
64
  return (
61
- <button class={twMerge(`${base} ${colors[color]} ${sizes[size]} ${contentLayout} ${loading} nitro-button ${className||''}`)} {...props}>
65
+ <button class={twMerge(`${base} ${sizes[size]} ${appliedColor} ${contentLayout} ${loading} nitro-button ${className||''}`)} {...props}>
62
66
  {IconLeft && getIcon(IconLeft)}
63
67
  {IconLeftEnd && getIcon(IconLeftEnd)}
64
68
  <span class={`${iconPosition == 'leftEnd' || iconPosition == 'rightEnd' ? 'flex-1' : ''}`}>{children}</span>
@@ -84,7 +84,7 @@ export const Filters = forwardRef<FiltersHandleType, FiltersProps>(({
84
84
  debouncedSubmit()
85
85
  }
86
86
 
87
- async function _onChange(e: {target: {id: string, value: unknown}}) {
87
+ async function _onChange(e: {target: {name: string, value: unknown}}) {
88
88
  await onChange(setState, e)
89
89
  debouncedSubmit()
90
90
  }
@@ -169,9 +169,9 @@ export const Filters = forwardRef<FiltersHandleType, FiltersProps>(({
169
169
  {
170
170
  !!count &&
171
171
  <span
172
- class={twMerge(`inline-flex items-center justify-center rounded-full text-xs text-white bg-primary w-[19px] h-[19px] ${buttonCounterClassName || ''}`)}
172
+ class={twMerge(`inline-flex items-center justify-center rounded-full text-white bg-primary box-content w-[1em] h-[1em] p-[2px] ${buttonCounterClassName || ''}`)}
173
173
  >
174
- {count}
174
+ <span class="text-xs">{count}</span>
175
175
  </span>
176
176
  }
177
177
  </span>
@@ -1,6 +1,6 @@
1
1
  type CheckboxProps = {
2
2
  name: string
3
- /** The id of the checkbox (used for radios) **/
3
+ /** name is applied if not provided. Used for radios */
4
4
  id?: string
5
5
  size?: 'md' | 'sm'
6
6
  subtext?: string|React.ReactNode
@@ -1,5 +1,5 @@
1
1
  // @ts-nocheck
2
- import { isRegex, deepFind, s3Image } from 'nitro-web/util'
2
+ import { deepFind, s3Image, getErrorFromState } from 'nitro-web/util'
3
3
  import { DropHandler } from 'nitro-web'
4
4
  import noImage from 'nitro-web/client/imgs/no-image.svg'
5
5
  import { Errors, MonasteryImage } from 'nitro-web/types'
@@ -7,12 +7,12 @@ import { Errors, MonasteryImage } from 'nitro-web/types'
7
7
  type DropProps = {
8
8
  awsUrl?: string
9
9
  className?: string
10
- /** Optional ID for the input element. Defaults to name if not provided */
11
- id?: string
12
10
  /** Field name or path on state (used to match errors), e.g. 'avatar', 'company.avatar' */
13
11
  name: string
12
+ /** Optional ID for the input element. Defaults to name if not provided */
13
+ id?: string
14
14
  /** Called when file is selected or dropped */
15
- onChange?: (event: { target: { id: string, value: File|FileList } }) => void
15
+ onChange?: (event: { target: { name: string, value: File|FileList } }) => void
16
16
  /** Whether to allow multiple file selection */
17
17
  multiple?: boolean
18
18
  /** State object to get the value and check errors against */
@@ -29,8 +29,8 @@ type Image = File | FileList | MonasteryImage | null
29
29
  export function Drop({ awsUrl, className, id, name, onChange, multiple, state, ...props }: DropProps) {
30
30
  if (!name) throw new Error('Drop component requires a `name` prop')
31
31
  let value: Image = null
32
- let error: Error | unknown
33
- const inputId = id ||name
32
+ const error = getErrorFromState(state, name)
33
+ const inputId = id || name
34
34
  const [urls, setUrls] = useState([])
35
35
  const stateRef = useRef(state)
36
36
  stateRef.current = state
@@ -40,12 +40,6 @@ export function Drop({ awsUrl, className, id, name, onChange, multiple, state, .
40
40
  else if (typeof state == 'object') value = deepFind(state, name) as Image
41
41
  if (typeof value == 'undefined') value = null
42
42
 
43
- // An error matches this input path
44
- for (const item of (state?.errors as Errors[] || [])) {
45
- if (isRegex(name) && (item.title||'').match(name)) error = item
46
- else if (item.title == name) error = item
47
- }
48
-
49
43
  useEffect(() => {
50
44
  (async () => setUrls(await getUrls(value as File | FileList | MonasteryImage | null)))()
51
45
  }, [value])
@@ -59,7 +53,7 @@ export function Drop({ awsUrl, className, id, name, onChange, multiple, state, .
59
53
  const errors = (stateRef?.current?.errors || []).filter((e: Errors[]) => e?.title != name)
60
54
  onChange({
61
55
  // remove file from state
62
- target: { id: name, value: null },
56
+ target: { name: name, value: null },
63
57
  // reset (server) errors
64
58
  errors: errors.length ? errors : undefined,
65
59
  })
@@ -68,7 +62,7 @@ export function Drop({ awsUrl, className, id, name, onChange, multiple, state, .
68
62
 
69
63
  async function onFileAttach (files: FileList) {
70
64
  // files is a FileList object
71
- if (onChange) onChange({ target: { id: name, value: multiple ? files : files[0] } })
65
+ if (onChange) onChange({ target: { name: name, value: multiple ? files : files[0] } })
72
66
  }
73
67
 
74
68
  async function getUrls(objectOrFileListItem: File | FileList | MonasteryImage | null) {
@@ -5,10 +5,11 @@ import { Dropdown, util } from 'nitro-web'
5
5
 
6
6
  export type FieldColorProps = React.InputHTMLAttributes<HTMLInputElement> & {
7
7
  name: string
8
+ /** name is applied if id is not provided */
8
9
  id?: string
9
10
  defaultColor?: string
10
11
  Icon?: React.ReactNode
11
- onChange?: (event: { target: { id: string, value: string|null } }) => void
12
+ onChange?: (event: { target: { name: string, value: string|null } }) => void
12
13
  value?: string|null
13
14
  }
14
15
 
@@ -17,7 +18,7 @@ export function FieldColor({ defaultColor='#333', Icon, onChange, value, ...prop
17
18
  const isInvalid = props.className?.includes('is-invalid') ? 'is-invalid' : ''
18
19
  const id = props.id || props.name
19
20
 
20
- function onInputChange(e: { target: { id: string, value: string|null } }) {
21
+ function onInputChange(e: { target: { name: string, value: string|null } }) {
21
22
  setLastChanged(`ic-${Date.now()}`)
22
23
  if (onChange) onChange(e)
23
24
  }
@@ -27,7 +28,7 @@ export function FieldColor({ defaultColor='#333', Icon, onChange, value, ...prop
27
28
  dir="bottom-left"
28
29
  menuToggles={false}
29
30
  menuContent={
30
- <ColorPicker key={lastChanged} defaultColor={defaultColor} id={id} name={props.name} value={value} onChange={onChange} />
31
+ <ColorPicker key={lastChanged} defaultColor={defaultColor} name={props.name} value={value} onChange={onChange} />
31
32
  }
32
33
  >
33
34
  <div className="grid grid-cols-1">
@@ -38,7 +39,7 @@ export function FieldColor({ defaultColor='#333', Icon, onChange, value, ...prop
38
39
  id={id}
39
40
  value={value}
40
41
  onChange={onInputChange}
41
- onBlur={() => !validHex(value||'') && onInputChange({ target: { id: id, value: '' }})}
42
+ onBlur={() => !validHex(value||'') && onInputChange({ target: { name: props.name, value: '' }})}
42
43
  autoComplete="off"
43
44
  type="text"
44
45
  />
@@ -47,12 +48,12 @@ export function FieldColor({ defaultColor='#333', Icon, onChange, value, ...prop
47
48
  )
48
49
  }
49
50
 
50
- function ColorPicker({ id='', onChange, value='', defaultColor='' }: FieldColorProps) {
51
+ function ColorPicker({ name='', onChange, value='', defaultColor='' }: FieldColorProps) {
51
52
  const [hsva, setHsva] = useState(() => hexToHsva(validHex(value) ? value : defaultColor))
52
53
  const [debounce] = useState(() => util.throttle(callOnChange, 50))
53
54
 
54
55
  function callOnChange(newHsva: HsvaColor) {
55
- if (onChange) onChange({ target: { id: id, value: hsvaToHex(newHsva) }})
56
+ if (onChange) onChange({ target: { name: name, value: hsvaToHex(newHsva) }})
56
57
  }
57
58
 
58
59
  return (
@@ -17,6 +17,7 @@ type NumericFormatProps = React.InputHTMLAttributes<HTMLInputElement> & {
17
17
 
18
18
  export type FieldCurrencyProps = NumericFormatProps & {
19
19
  name: string
20
+ /** name is applied if id is not provided */
20
21
  id?: string
21
22
  /** e.g. { currencies: { nzd: { symbol: '$', digits: 2 } } } (check out the nitro example for more info) */
22
23
  config: {
@@ -25,7 +26,7 @@ export type FieldCurrencyProps = NumericFormatProps & {
25
26
  }
26
27
  /** currency iso, e.g. 'nzd' */
27
28
  currency: string
28
- onChange?: (event: { target: { id: string, value: string|number|null } }) => void
29
+ onChange?: (event: { target: { name: string, value: string|number|null } }) => void
29
30
  /** value should be in cents */
30
31
  value?: string|number|null
31
32
  defaultValue?: number | string | null
@@ -138,7 +139,7 @@ export function FieldCurrency({ config, currency='nzd', onChange, value, default
138
139
  onValueChange={!onChange ? undefined : ({ floatValue }, e) => {
139
140
  // console.log('onValueChange', floatValue, e)
140
141
  if (e.source === 'event') setDontFix(true)
141
- onChange({ target: { id: id, value: toCents(floatValue) }})
142
+ onChange({ target: { name: props.name, value: toCents(floatValue) }})
142
143
  }}
143
144
  onBlur={() => { setDollars(toDollars(value, true))}}
144
145
  placeholder={props.placeholder || '0.00'}
@@ -148,7 +149,7 @@ export function FieldCurrency({ config, currency='nzd', onChange, value, default
148
149
  defaultValue={defaultValue}
149
150
  />
150
151
  <span
151
- class={`absolute top-0 bottom-0 left-3 inline-flex items-center select-none text-gray-500 text-sm text-sm-input ${dollars !== null && settings.prefix == '$' ? 'text-foreground' : ''}`}
152
+ class={`absolute top-0 bottom-0 left-3 inline-flex items-center select-none text-gray-500 text-input-size ${dollars !== null && settings.prefix == '$' ? 'text-foreground' : ''}`}
152
153
  >
153
154
  {settings.prefix || settings.suffix}
154
155
  </span>
@@ -11,7 +11,8 @@ type DropdownRef = {
11
11
  type PreFieldDateProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> & {
12
12
  name: string
13
13
  mode: Mode
14
- id?: string
14
+ // name is applied if id is not provided
15
+ id?: string
15
16
  showTime?: boolean
16
17
  prefix?: string
17
18
  numberOfMonths?: number
@@ -22,11 +23,11 @@ type PreFieldDateProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onCh
22
23
  // An array is returned for mode = 'multiple' or 'range'
23
24
  export type FieldDateProps = (
24
25
  | ({ mode: 'single' } & PreFieldDateProps & {
25
- onChange?: (e: { target: { id: string, value: null|number } }) => void
26
+ onChange?: (e: { target: { name: string, value: null|number } }) => void
26
27
  value?: null|number|string
27
28
  })
28
29
  | ({ mode: 'multiple' | 'range' } & PreFieldDateProps & {
29
- onChange: (e: { target: { id: string, value: (null|number)[] } }) => void
30
+ onChange: (e: { target: { name: string, value: (null|number)[] } }) => void
30
31
  value?: null|number|string|(null|number|string)[]
31
32
  })
32
33
  )
@@ -42,9 +43,9 @@ export function FieldDate({
42
43
  const localePattern = `d MMM yyyy${showTime && mode == 'single' ? ' hh:mmaa' : ''}`
43
44
  const [prefixWidth, setPrefixWidth] = useState(0)
44
45
  const dropdownRef = useRef<DropdownRef>(null)
45
- const id = props.id || props.name
46
46
  const [month, setMonth] = useState<number|undefined>()
47
47
  const [lastUpdated, setLastUpdated] = useState(0)
48
+ const id = props.id || props.name
48
49
 
49
50
  // Convert the value to an array of valid* dates
50
51
  const dates = useMemo(() => {
@@ -70,7 +71,7 @@ export function FieldDate({
70
71
  setInputValue(getInputValue(value))
71
72
  // Update the value
72
73
  if (onChange) {
73
- onChange({ target: { id: id, value: value as any } })
74
+ onChange({ target: { name: props.name, value: value as any } })
74
75
  setLastUpdated(new Date().getTime())
75
76
  }
76
77
  }
@@ -104,7 +105,7 @@ export function FieldDate({
104
105
  // Update the value
105
106
  const value = mode == 'single' ? split[0]?.getTime() ?? null : split.map(d => d?.getTime() ?? null)
106
107
  if (onChange) {
107
- onChange({ target: { id: id, value: value as any }})
108
+ onChange({ target: { name: props.name, value: value as any }})
108
109
  setLastUpdated(new Date().getTime())
109
110
  }
110
111
  }
@@ -134,7 +135,7 @@ export function FieldDate({
134
135
  {
135
136
  prefix &&
136
137
  // Similar classNames to the input.tsx:IconWrapper()
137
- <span className="z-[0] col-start-1 row-start-1 self-center select-none justify-self-start text-sm text-sm-input ml-3">
138
+ <span className="z-[0] col-start-1 row-start-1 self-center select-none justify-self-start text-input-size ml-3">
138
139
  {prefix}
139
140
  </span>
140
141
  }
@@ -144,11 +145,11 @@ export function FieldDate({
144
145
  id={id}
145
146
  autoComplete="off"
146
147
  className={(props.className||'')}// + props.className?.includes('is-invalid') ? ' is-invalid' : ''}
147
- value={inputValue}
148
- onChange={onInputChange}
149
148
  onBlur={() => setInputValue(getInputValue(dates))}
149
+ onChange={onInputChange}
150
150
  style={{ textIndent: prefixWidth + 'px' }}
151
151
  type="text"
152
+ value={inputValue}
152
153
  />
153
154
  </div>
154
155
  </Dropdown>
@@ -1,15 +1,10 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import { css } from 'twin.macro'
3
- import { util, FieldCurrency, FieldCurrencyProps, FieldColor, FieldColorProps, FieldDate, FieldDateProps, twMerge } from 'nitro-web'
3
+ import { FieldCurrency, FieldCurrencyProps, FieldColor, FieldColorProps, FieldDate, FieldDateProps } from 'nitro-web'
4
+ import { twMerge, getErrorFromState, deepFind } from 'nitro-web/util'
4
5
  import { Errors, type Error } from 'nitro-web/types'
5
- import {
6
- EnvelopeIcon,
7
- CalendarIcon,
8
- FunnelIcon,
9
- MagnifyingGlassIcon,
10
- EyeIcon,
11
- EyeSlashIcon,
12
- } from '@heroicons/react/20/solid'
6
+ import { EnvelopeIcon, CalendarIcon, FunnelIcon, MagnifyingGlassIcon, EyeIcon, EyeSlashIcon } from '@heroicons/react/20/solid'
7
+ import { memo } from 'react'
13
8
  // Maybe use fill-current tw class for lucide icons (https://github.com/lucide-icons/lucide/discussions/458)
14
9
 
15
10
  type InputProps = React.InputHTMLAttributes<HTMLInputElement>
@@ -17,12 +12,15 @@ type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>
17
12
  type FieldExtraProps = {
18
13
  // field name or path on state (used to match errors), e.g. 'date', 'company.email'
19
14
  name: string
15
+ // name is applied if id is not provided
20
16
  id?: string
21
17
  // state object to get the value, and check errors against
22
18
  state?: { errors?: Errors, [key: string]: any }
23
19
  type?: 'text' | 'password' | 'email' | 'filter' | 'search' | 'textarea' | 'currency' | 'date' | 'color'
24
20
  icon?: React.ReactNode
25
21
  iconPos?: 'left' | 'right'
22
+ /** Pass dependencies to break memoization, handy for onChange/onInputChange **/
23
+ deps?: unknown[]
26
24
  }
27
25
  type IconWrapperProps = {
28
26
  iconPos: string
@@ -37,12 +35,21 @@ export type FieldProps = (
37
35
  | ({ type: 'color' } & FieldColorProps & FieldExtraProps)
38
36
  | ({ type: 'date' } & FieldDateProps & FieldExtraProps)
39
37
  )
38
+ type IsFieldCachedProps = {
39
+ name: string
40
+ state?: FieldProps['state']
41
+ deps?: FieldProps['deps']
42
+ }
43
+
44
+ export const Field = memo(FieldBase, (prev, next) => {
45
+ return isFieldCached(prev, next)
46
+ })
40
47
 
41
- export function Field({ state, icon, iconPos: ip, ...props }: FieldProps) {
48
+ function FieldBase({ state, icon, iconPos: ip, ...props }: FieldProps) {
42
49
  // type must be kept as props.type for TS to be happy and follow the conditions below
43
- let error!: Error
44
50
  let value!: string
45
51
  let Icon!: React.ReactNode
52
+ const error = getErrorFromState(state, props.name)
46
53
  const type = props.type
47
54
  const iconPos = ip == 'left' || (type == 'color' && !ip) ? 'left' : 'right'
48
55
 
@@ -58,16 +65,10 @@ export function Field({ state, icon, iconPos: ip, ...props }: FieldProps) {
58
65
  // Value: Input is always controlled if state is passed in
59
66
  if (props.value) value = props.value as string
60
67
  else if (typeof state == 'object') {
61
- const v = util.deepFind(state, props.name) as string | undefined
68
+ const v = deepFind(state, props.name) as string | undefined
62
69
  value = v ?? ''
63
70
  }
64
71
 
65
- // Errors: find any that match this field path
66
- for (const item of (state?.errors || [])) {
67
- if (util.isRegex(props.name) && (item.title || '').match(props.name)) error = item
68
- else if (item.title == props.name) error = item
69
- }
70
-
71
72
  // Icon
72
73
  if (type == 'password') {
73
74
  Icon = <IconWrapper
@@ -92,7 +93,7 @@ export function Field({ state, icon, iconPos: ip, ...props }: FieldProps) {
92
93
 
93
94
  // Classname
94
95
  const inputClassName = getInputClasses({ error, Icon, iconPos, type })
95
- const commonProps = { id: props.name || props.id, value: value, className: inputClassName }
96
+ const commonProps = { id: props.id || props.name, value: value, className: inputClassName }
96
97
 
97
98
  // Type has to be referenced as props.type for TS to be happy
98
99
  if (!type || type == 'text' || type == 'password' || type == 'email' || type == 'filter' || type == 'search') {
@@ -137,14 +138,14 @@ function FieldContainer({ children, className, error }: { children: React.ReactN
137
138
  )
138
139
  }
139
140
 
140
- function getInputClasses({ error, Icon, iconPos, type }: { error: Error, Icon?: React.ReactNode, iconPos: string, type?: string }) {
141
- const pl = 'pl-3 pl-input-x'
142
- const pr = 'pr-3 pr-input-x'
143
- const py = 'py-[0.58rem] py-input-y'
141
+ function getInputClasses({ error, Icon, iconPos, type }: { error?: Error, Icon?: React.ReactNode, iconPos: string, type?: string }) {
142
+ const pl = 'pl-[12px] pl-input-x'
143
+ const pr = 'pr-[12px] pr-input-x'
144
+ const py = 'py-[9px] py-input-y'
144
145
  const plWithIcon = type == 'color' ? 'pl-9' : 'pl-8' // was sm:pl-8 pl-8, etc
145
146
  const prWithIcon = type == 'color' ? 'pr-9' : 'pr-8'
146
147
  return (
147
- `block ${py} col-start-1 row-start-1 w-full rounded-md bg-white text-sm text-sm-input outline outline-1 -outline-offset-1 ` +
148
+ `block ${py} col-start-1 row-start-1 w-full rounded-md bg-white text-input-size outline outline-1 -outline-offset-1 ` +
148
149
  'placeholder:text-input-placeholder focus:outline focus:outline-2 focus:-outline-offset-2 ' +
149
150
  (iconPos == 'right' && Icon ? `${pl} ${prWithIcon} ` : (Icon ? `${plWithIcon} ${pr} ` : `${pl} ${pr} `)) +
150
151
  (error
@@ -175,6 +176,36 @@ function ColorSvg({ hex }: { hex?: string }) {
175
176
  )
176
177
  }
177
178
 
179
+ export function isFieldCached(prev: IsFieldCachedProps, next: IsFieldCachedProps) {
180
+ const path = prev.name
181
+ const state = prev.state || {}
182
+ // If state/satte-error values have changed, re-render!
183
+ if (deepFind(state, path) !== deepFind(next.state || {}, path)) {
184
+ // console.log(1, 'state changed', path)
185
+ return false
186
+ }
187
+ if (getErrorFromState(state, path) !== getErrorFromState(next.state || {}, path)) {
188
+ // console.log(2, 'error changed', path)
189
+ return false
190
+ }
191
+ // If deps have changed, re-render!
192
+ if ((next.deps?.length !== prev.deps?.length) || next.deps?.some((v, i) => v !== prev.deps?.[i])) {
193
+ // console.log(3, 'deps changed', path)
194
+ return false
195
+ }
196
+ // If any other props have changed, except onChange/onInputChange, re-render!
197
+ // In most cases, onChange/onInputChange remain the same and reference the same function...
198
+ for (const k in prev) {
199
+ if (k === 'state' || k === 'onChange' || k === 'onInputChange') continue
200
+ if (prev[k as keyof typeof prev] !== next[k as keyof typeof next]) {
201
+ // console.log(4, 'changed', path, k)
202
+ return false
203
+ }
204
+ }
205
+ // All good, use cached version
206
+ return true
207
+ }
208
+
178
209
  const style = css`
179
210
  input {
180
211
  appearance: textfield;
@@ -1,7 +1,7 @@
1
1
  import { Errors } from 'nitro-web/types'
2
2
 
3
3
  type FormError = {
4
- state: { errors: Errors },
4
+ state: { errors?: Errors },
5
5
  // display all errors except these field titles, e.g. ['name', 'address']
6
6
  fields?: Array<string>,
7
7
  className?: string,
@@ -1,9 +1,11 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
  import { css } from 'twin.macro'
2
- import { twMerge } from 'nitro-web'
3
- import ReactSelect, { components, ControlProps, createFilter, OptionProps, SingleValueProps } from 'react-select'
4
- import { ClearIndicatorProps, DropdownIndicatorProps, MultiValueRemoveProps } from 'react-select'
3
+ import { memo } from 'react'
4
+ import ReactSelect, { components, ControlProps, createFilter, OptionProps, SingleValueProps, ClearIndicatorProps,
5
+ DropdownIndicatorProps, MultiValueRemoveProps } from 'react-select'
5
6
  import { ChevronUpDownIcon, CheckCircleIcon, XMarkIcon } from '@heroicons/react/20/solid'
6
- import { util } from 'nitro-web'
7
+ import { isFieldCached } from 'nitro-web'
8
+ import { getErrorFromState, deepFind, twMerge } from 'nitro-web/util'
7
9
  import { Errors } from 'nitro-web/types'
8
10
 
9
11
  const filterFn = createFilter()
@@ -27,35 +29,38 @@ type SelectProps = {
27
29
  /** The prefix to add to the input **/
28
30
  prefix?: string
29
31
  /** The onChange handler **/
30
- onChange?: (event: { target: { id: string, value: unknown } }) => void
32
+ onChange?: (event: { target: { name: string, value: unknown } }) => void
31
33
  /** The options to display in the dropdown **/
32
34
  options: { value: unknown, label: string | React.ReactNode, fixed?: boolean, [key: string]: unknown }[]
33
35
  /** The state object to get the value and check errors from **/
34
- state?: { errors?: Errors, [key: string]: unknown }
36
+ state?: { errors?: Errors, [key: string]: any } // was unknown|unknown[]
35
37
  /** Select variations **/
36
38
  type?: 'country'|'customer'|''
39
+ /** Pass dependencies to break memoization, handy for onChange/onInputChange **/
40
+ deps?: unknown[]
37
41
  /** All other props are passed to react-select **/
38
42
  [key: string]: unknown
39
43
  }
40
44
 
41
- export function Select({ inputId, minMenuWidth, name, prefix='', onChange, options, state, type='', ...props }: SelectProps) {
42
- let value: unknown
43
- let hasError: { title: string, detail: string } | null = null
45
+ export const Select = memo(SelectBase, (prev, next) => {
46
+ return isFieldCached(prev, next)
47
+ })
48
+
49
+ function SelectBase({ inputId, minMenuWidth, name, prefix='', onChange, options, state, type='', ...props }: SelectProps) {
50
+ let value: unknown|unknown[]
51
+ const error = getErrorFromState(state, name)
44
52
  if (!name) throw new Error('Select component requires a `name` and `options` prop')
45
53
 
54
+ // Get value from value or state
55
+ if ('value' in props) value = props.value
56
+ else if (typeof state == 'object') value = deepFind(state, name)
57
+
58
+ // If multi-select, filter options by value
59
+ if (Array.isArray(value)) value = options.filter(o => (value as unknown[]).includes(o.value))
60
+ else value = options.find(o => value === o.value)
61
+
46
62
  // Input is always controlled if state is passed in
47
- if (props.value) {
48
- value = props.value
49
- } else if (typeof state == 'object') {
50
- value = options.find(o => o.value == util.deepFind(state, name))
51
- if (typeof value == 'undefined') value = ''
52
- }
53
-
54
- // An error matches this input path
55
- for (const item of (state?.errors || [])) {
56
- if (util.isRegex(name) && (item.title||'').match(name)) hasError = item
57
- else if (item.title == name) hasError = item
58
- }
63
+ if (typeof state == 'object' && typeof value == 'undefined') value = ''
59
64
 
60
65
  return (
61
66
  <div css={style} class={twMerge(`mt-2.5 mb-6 mt-input-before mb-input-after nitro-select ${props.className||''}`)}>
@@ -83,20 +88,26 @@ export function Select({ inputId, minMenuWidth, name, prefix='', onChange, optio
83
88
  }}
84
89
  menuPlacement="auto"
85
90
  minMenuHeight={250}
86
- onChange={!onChange ? undefined : (o) => onChange({ target: { id: inputId || name, value: (o as {value?: unknown})?.value || o }})}
91
+ onChange={!onChange ? undefined : (o) => {
92
+ // Array returned for multi-select
93
+ const value = Array.isArray(o)
94
+ ? o.map(v => typeof v == 'object' && v !== null && 'value' in v ? v.value : v)
95
+ : (typeof o == 'object' && o !== null && 'value' in o ? o.value : o)
96
+ return onChange({ target: { name: name, value: value }})
97
+ }}
87
98
  options={options}
88
99
  value={value}
89
100
  classNames={{
90
101
  // Input container
91
- control: (p) => getSelectStyle({ name: 'control', hasError: !!hasError, ...p }),
102
+ control: (p) => getSelectStyle({ name: 'control', hasError: !!error, ...p }),
92
103
  valueContainer: () => getSelectStyle({ name: 'valueContainer' }),
93
104
  // Input container objects
94
- input: () => getSelectStyle({ name: 'input', hasError: !!hasError }),
105
+ input: () => getSelectStyle({ name: 'input', hasError: !!error }),
95
106
  multiValue: () => getSelectStyle({ name: 'multiValue' }),
96
107
  multiValueLabel: () => '',
97
108
  multiValueRemove: () => getSelectStyle({ name: 'multiValueRemove' }),
98
109
  placeholder: () => getSelectStyle({ name: 'placeholder' }),
99
- singleValue: () => getSelectStyle({ name: 'singleValue', hasError: !!hasError }),
110
+ singleValue: () => getSelectStyle({ name: 'singleValue', hasError: !!error }),
100
111
  // Indicators
101
112
  clearIndicator: () => getSelectStyle({ name: 'clearIndicator' }),
102
113
  dropdownIndicator: () => getSelectStyle({ name: 'dropdownIndicator' }),
@@ -140,7 +151,7 @@ export function Select({ inputId, minMenuWidth, name, prefix='', onChange, optio
140
151
  // isDisabled={true}
141
152
  // maxMenuHeight={200}
142
153
  />
143
- {hasError && <div class="mt-1.5 text-xs text-danger">{hasError.detail}</div>}
154
+ {error && <div class="mt-1.5 text-xs text-danger">{error.detail}</div>}
144
155
  </div>
145
156
  )
146
157
  }
@@ -211,7 +222,7 @@ const DropdownIndicator = (props: DropdownIndicatorProps) => {
211
222
  const ClearIndicator = (props: ClearIndicatorProps) => {
212
223
  return (
213
224
  <components.ClearIndicator {...props}>
214
- <XMarkIcon className="size-4 my-0.5" />
225
+ <XMarkIcon className="size-4" />
215
226
  </components.ClearIndicator>
216
227
  )
217
228
  }
@@ -219,7 +230,7 @@ const ClearIndicator = (props: ClearIndicatorProps) => {
219
230
  const MultiValueRemove = (props: MultiValueRemoveProps) => {
220
231
  return (
221
232
  <components.MultiValueRemove {...props}>
222
- <XMarkIcon className="size-4 p-[1px]" />
233
+ <XMarkIcon className="size-[1em] p-[1px]" />
223
234
  </components.MultiValueRemove>
224
235
  )
225
236
  }
@@ -237,18 +248,19 @@ const selectStyles = {
237
248
  // Based off https://www.jussivirtanen.fi/writing/styling-react-select-with-tailwind
238
249
  // Input container
239
250
  control: {
240
- base: 'rounded-md bg-white hover:cursor-pointer text-sm text-sm-input outline outline-1 -outline-offset-1 outline-input-border',
251
+ base: 'rounded-md bg-white hover:cursor-pointer text-input-size outline outline-1 -outline-offset-1 '
252
+ + '!min-h-0 outline-input-border',
241
253
  focus: 'outline-2 -outline-offset-2 outline-input-border-focus',
242
254
  error: 'outline-danger',
243
255
  },
244
- valueContainer: 'py-[0.58rem] px-3 py-input-y px-input-x gap-1',
256
+ valueContainer: 'py-[9px] px-[12px] py-input-y px-input-x gap-1',
245
257
  // Input container objects
246
258
  input: {
247
259
  base: 'text-input',
248
260
  error: 'text-red-900',
249
261
  },
250
262
  multiValue: 'bg-primary text-white rounded items-center pl-2 pr-1.5 gap-1.5',
251
- multiValueLabel: '',
263
+ multiValueLabel: 'text-xs',
252
264
  multiValueRemove: 'border border-black/10 bg-clip-content bg-white rounded-md text-foreground hover:bg-red-50',
253
265
  placeholder: 'text-input-placeholder',
254
266
  singleValue: {
@@ -261,8 +273,8 @@ const selectStyles = {
261
273
  indicatorsContainer: 'p-1 px-2 gap-1',
262
274
  indicatorSeparator: 'py-0.5 before:content-[""] before:block before:bg-gray-100 before:w-px before:h-full',
263
275
  // Dropdown menu
264
- menu: 'mt-1.5 border border-dropdown-ul-border bg-white rounded-md text-sm text-sm-input overflow-hidden shadow-dropdown-ul',
265
- groupHeading: 'ml-3 mt-2 mb-1 text-gray-500 text-sm text-sm-input',
276
+ menu: 'mt-1.5 border border-dropdown-ul-border bg-white rounded-md text-input-size overflow-hidden shadow-dropdown-ul',
277
+ groupHeading: 'ml-3 mt-2 mb-1 text-gray-500 text-input-size',
266
278
  noOptionsMessage: 'm-1 text-gray-500 p-2 bg-gray-50 border border-dashed border-gray-200 rounded-sm',
267
279
  option: {
268
280
  base: 'relative px-3 py-2 !flex items-center gap-2 cursor-default',
@@ -1,18 +1,26 @@
1
1
  import {
2
- Drop, Dropdown, Field, Select, Button, Checkbox, GithubLink, Modal, Calendar, injectedConfig,
3
- Filters, FiltersHandleType,
4
- FilterType,
2
+ Drop, Dropdown, Field, Select, Button as ButtonNitro, Checkbox, GithubLink, Modal, Calendar, injectedConfig,
3
+ Filters, FiltersHandleType, FilterType,
5
4
  } from 'nitro-web'
6
5
  import { getCountryOptions, getCurrencyOptions, ucFirst } from 'nitro-web/util'
7
6
  import { Check } from 'lucide-react'
8
7
 
9
- export function Styleguide({ className }: { className?: string }) {
8
+ type StyleguideProps = {
9
+ className?: string
10
+ elements?: {
11
+ Button?: typeof ButtonNitro
12
+ }
13
+ children?: React.ReactNode
14
+ }
15
+
16
+ export function Styleguide({ className, elements, children }: StyleguideProps) {
10
17
  const [customerSearch, setCustomerSearch] = useState('')
11
18
  const [showModal1, setShowModal1] = useState(false)
12
19
  const [state, setState] = useState({
13
20
  address: '',
14
21
  amount: 100,
15
22
  brandColor: '#F3CA5F',
23
+ colorsMulti: ['blue', 'green'],
16
24
  country: 'nz',
17
25
  currency: 'nzd', // can be commented too
18
26
  date: Date.now(),
@@ -26,7 +34,7 @@ export function Styleguide({ className }: { className?: string }) {
26
34
  })
27
35
  const [filterState, setFilterState] = useState({})
28
36
  const filtersRef = useRef<FiltersHandleType>(null)
29
- const filters: FilterType[] = [
37
+ const filters: FilterType[] = useMemo(() => [
30
38
  {
31
39
  name: 'dateRange',
32
40
  type: 'date',
@@ -46,16 +54,9 @@ export function Styleguide({ className }: { className?: string }) {
46
54
  { label: 'Rejected', value: 'rejected' },
47
55
  ],
48
56
  },
49
- ]
57
+ ], [])
50
58
 
51
- // Example of updating state
52
- // useEffect(() => {
53
- // setTimeout(() => {
54
- // setState({ ...state, amount: 123456, currency: 'usd', brandColor: '#8656ED' })
55
- // }, 2000)
56
- // }, [])
57
-
58
- const options = [
59
+ const options = useMemo(() => [
59
60
  { label: 'Open customer preview' },
60
61
  { label: 'Add a payment', isSelected: true },
61
62
  { label: 'Email invoice' },
@@ -63,10 +64,12 @@ export function Styleguide({ className }: { className?: string }) {
63
64
  { label: 'Edit' },
64
65
  { label: 'Copy' },
65
66
  { label: 'Delete' },
66
- ]
67
+ ], [])
67
68
 
68
- function onCustomerInputChange (e: { target: { id: string, value: unknown } }) {
69
- if (e.target.id == 'customer' && e.target.value == '0') {
69
+ const Button = elements?.Button || ButtonNitro
70
+
71
+ function onCustomerInputChange (e: { target: { name: string, value: unknown } }) {
72
+ if (e.target.name == 'customer' && e.target.value == '0') {
70
73
  setCustomerSearch('')
71
74
  e.target.value = null // clear the select's selected value
72
75
  setTimeout(() => alert('Adding new customer...'), 0)
@@ -78,6 +81,13 @@ export function Styleguide({ className }: { className?: string }) {
78
81
  setCustomerSearch(search || '')
79
82
  }
80
83
 
84
+ // Example of updating state
85
+ // useEffect(() => {
86
+ // setTimeout(() => {
87
+ // setState({ ...state, amount: 123456, currency: 'usd', brandColor: '#8656ED' })
88
+ // }, 2000)
89
+ // }, [])
90
+
81
91
  return (
82
92
  <div class={`text-left max-w-[1100px] ${className}`}>
83
93
  <GithubLink filename={__filename} />
@@ -139,7 +149,8 @@ export function Styleguide({ className }: { className?: string }) {
139
149
  <Field
140
150
  class="!my-0 min-w-[242px]"
141
151
  type="search"
142
- name="search"
152
+ name="search"
153
+ id="search2"
143
154
  iconPos="left"
144
155
  state={filterState}
145
156
  onChange={(e) => {
@@ -154,7 +165,10 @@ export function Styleguide({ className }: { className?: string }) {
154
165
  <div class="flex flex-wrap gap-x-6 gap-y-4 mb-10">
155
166
  <div><Button color="primary">primary (default)</Button></div>
156
167
  <div><Button color="secondary">secondary button</Button></div>
168
+ <div><Button color="black">black button</Button></div>
169
+ <div><Button color="dark">dark button</Button></div>
157
170
  <div><Button color="white">white button</Button></div>
171
+ <div><Button color="clear">clear button</Button></div>
158
172
  <div><Button color="primary" size="xs">*-xs button</Button></div>
159
173
  <div><Button color="primary" size="sm">*-sm button</Button></div>
160
174
  <div><Button color="primary">*-md (default)</Button></div>
@@ -196,22 +210,31 @@ export function Styleguide({ className }: { className?: string }) {
196
210
  // menuIsOpen={true}
197
211
  name="action"
198
212
  isSearchable={false}
199
- options={[
213
+ options={useMemo(() => [
200
214
  { value: 'edit', label: 'Edit' },
201
215
  { value: 'delete', label: 'Delete' },
202
- ]}
216
+ ], [])}
203
217
  />
204
218
  </div>
205
219
  <div>
206
- <label for="multi">Mutli Select</label>
220
+ <label for="colorsMulti">Mutli Select</label>
207
221
  <Select
208
- name="multi"
222
+ name="colorsMulti"
209
223
  isMulti={true}
210
- options={[
224
+ state={state}
225
+ options={useMemo(() => [
211
226
  { value: 'blue', label: 'Blue' },
212
227
  { value: 'green', label: 'Green' },
213
228
  { value: 'yellow', label: 'Yellow' },
214
- ]}
229
+ { value: 'red', label: 'Red' },
230
+ { value: 'orange', label: 'Orange' },
231
+ { value: 'purple', label: 'Purple' },
232
+ { value: 'pink', label: 'Pink' },
233
+ { value: 'gray', label: 'Gray' },
234
+ { value: 'black', label: 'Black' },
235
+ { value: 'white', label: 'White' },
236
+ ], [])}
237
+ onChange={(e) => onChange(setState, e)}
215
238
  />
216
239
  </div>
217
240
  <div>
@@ -235,7 +258,7 @@ export function Styleguide({ className }: { className?: string }) {
235
258
  state={state}
236
259
  onChange={onCustomerInputChange}
237
260
  onInputChange={onCustomerSearch}
238
- options={[
261
+ options={useMemo(() => [
239
262
  {
240
263
  className: 'bb',
241
264
  fixed: true,
@@ -250,7 +273,7 @@ export function Styleguide({ className }: { className?: string }) {
250
273
  { value: '1', label: 'Iron Man Industries' },
251
274
  { value: '2', label: 'Captain America' },
252
275
  { value: '3', label: 'Thor Limited' },
253
- ]}
276
+ ], [customerSearch])}
254
277
  />
255
278
  </div>
256
279
  <div>
@@ -282,8 +305,8 @@ export function Styleguide({ className }: { className?: string }) {
282
305
  <Field name="password" type="password"/>
283
306
  </div>
284
307
  <div>
285
- <label for="search">Search</label>
286
- <Field name="search" type="search" placeholder="Search..." />
308
+ <label for="search3">Search</label>
309
+ <Field name="search" id="search3" type="search" placeholder="Search..." />
287
310
  </div>
288
311
  <div>
289
312
  <label for="filter">Filter by Code</label>
@@ -324,22 +347,6 @@ export function Styleguide({ className }: { className?: string }) {
324
347
  </div>
325
348
  </div>
326
349
 
327
- <h2 class="h3">File Inputs & Calendar</h2>
328
- <div class="grid grid-cols-3 gap-x-6">
329
- <div>
330
- <label for="avatar">Avatar</label>
331
- <Drop class="is-small" name="avatar" state={state} onChange={(e) => onChange(setState, e)} awsUrl={injectedConfig.awsUrl} />
332
- </div>
333
- <div>
334
- <label for="calendar">Calendar</label>
335
- <Calendar mode="range" value={state.calendar} numberOfMonths={1}
336
- onChange={(mode, value) => {
337
- onChange(setState, { target: { id: 'calendar', value: value } })
338
- }}
339
- />
340
- </div>
341
- </div>
342
-
343
350
  <Modal show={showModal1} setShow={setShowModal1} class="p-9">
344
351
  <h3 class="h3">Edit Profile</h3>
345
352
  <p class="mb-5">An example modal containing a basic form for editing profiles.</p>
@@ -357,6 +364,24 @@ export function Styleguide({ className }: { className?: string }) {
357
364
  <Button color="primary" onClick={() => setShowModal1(false)}>Save</Button>
358
365
  </div>
359
366
  </Modal>
367
+
368
+ <h2 class="h3">File Inputs & Calendar</h2>
369
+ <div class="grid grid-cols-3 gap-x-6 mb-4 last:mb-0">
370
+ <div>
371
+ <label for="avatar">Avatar</label>
372
+ <Drop class="is-small" name="avatar" state={state} onChange={(e) => onChange(setState, e)} awsUrl={injectedConfig.awsUrl} />
373
+ </div>
374
+ <div>
375
+ <label for="calendar">Calendar</label>
376
+ <Calendar mode="range" value={state.calendar} numberOfMonths={1}
377
+ onChange={(mode, value) => {
378
+ onChange(setState, { target: { name: 'calendar', value: value } })
379
+ }}
380
+ />
381
+ </div>
382
+ </div>
383
+
384
+ {children}
360
385
  </div>
361
386
  )
362
387
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitro-web",
3
- "version": "0.0.49",
3
+ "version": "0.0.51",
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 🚀",
package/types/util.d.ts CHANGED
@@ -257,6 +257,21 @@ export function getCurrencyOptions(currencies: {
257
257
  value: string;
258
258
  label: string;
259
259
  }[];
260
+ /**
261
+ * Returns an error from a state object matching the path
262
+ * @param {{ errors?: { title: string, detail: string }[] }|undefined} state
263
+ * @param {string} path
264
+ * @returns {{ title: string, detail: string }|undefined}
265
+ */
266
+ export function getErrorFromState(state: {
267
+ errors?: {
268
+ title: string;
269
+ detail: string;
270
+ }[];
271
+ } | undefined, path: string): {
272
+ title: string;
273
+ detail: string;
274
+ } | undefined;
260
275
  /**
261
276
  * Get the width of a prefix
262
277
  * @param {string} prefix
@@ -466,7 +481,7 @@ export function omit(obj: {
466
481
  *
467
482
  * @template T
468
483
  * @param {React.Dispatch<React.SetStateAction<T>>} setState
469
- * @param {{target: {id: string, value: unknown}}|[string, function|unknown]} eventOrPathValue
484
+ * @param {{target: {name: string, value: unknown}}|[string, function|unknown]} eventOrPathValue
470
485
  * @param {Function} [beforeSetState] - optional function to run before setting the state
471
486
  * @returns {Promise<T>}
472
487
  *
@@ -476,7 +491,7 @@ export function omit(obj: {
476
491
  */
477
492
  export function onChange<T>(setState: React.Dispatch<React.SetStateAction<T>>, eventOrPathValue: {
478
493
  target: {
479
- id: string;
494
+ name: string;
480
495
  value: unknown;
481
496
  };
482
497
  } | [string, Function | unknown], beforeSetState?: Function): Promise<T>;
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../util.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../util.js"],"names":[],"mappings":"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"}
package/types.ts CHANGED
@@ -31,7 +31,7 @@ export type User = {
31
31
  }
32
32
 
33
33
  export type Error = { title: string, detail: string }
34
- export type Errors = Array<Error> | null
34
+ export type Errors = Error[]
35
35
 
36
36
  export type MonasteryImage = {
37
37
  url: string
package/util.js CHANGED
@@ -681,6 +681,20 @@ export function getCurrencyOptions (currencies) {
681
681
  return output
682
682
  }
683
683
 
684
+ /**
685
+ * Returns an error from a state object matching the path
686
+ * @param {{ errors?: { title: string, detail: string }[] }|undefined} state
687
+ * @param {string} path
688
+ * @returns {{ title: string, detail: string }|undefined}
689
+ */
690
+ export function getErrorFromState (state, path) {
691
+ if (!state || !state.errors) return undefined
692
+ for (const item of state.errors) {
693
+ if (isRegex(path) && (item.title || '').match(path)) return item
694
+ else if (item.title == path) return item
695
+ }
696
+ }
697
+
684
698
  /**
685
699
  * Get the width of a prefix
686
700
  * @param {string} prefix
@@ -1106,7 +1120,7 @@ export function omit (obj, fields) {
1106
1120
  *
1107
1121
  * @template T
1108
1122
  * @param {React.Dispatch<React.SetStateAction<T>>} setState
1109
- * @param {{target: {id: string, value: unknown}}|[string, function|unknown]} eventOrPathValue
1123
+ * @param {{target: {name: string, value: unknown}}|[string, function|unknown]} eventOrPathValue
1110
1124
  * @param {Function} [beforeSetState] - optional function to run before setting the state
1111
1125
  * @returns {Promise<T>}
1112
1126
  *
@@ -1124,7 +1138,7 @@ export function onChange (setState, eventOrPathValue, beforeSetState) {
1124
1138
 
1125
1139
  if (typeof eventOrPathValue === 'object' && 'target' in eventOrPathValue) {
1126
1140
  const element = /** @type {HTMLInputElement & {_value?: unknown}} */(eventOrPathValue.target) // we need to assume this is an input
1127
- chunks = (element.id || element.name).split('.')
1141
+ chunks = (element.name || element.id).split('.')
1128
1142
  hasFiles = !!element.files
1129
1143
  value = element.files
1130
1144
  ? element.files[0]
@@ -1561,7 +1575,20 @@ export function trim (string) {
1561
1575
  */
1562
1576
  export function twMerge(...args) {
1563
1577
  const ignoredClasses = /** @type {string[]} */([])
1564
- const ignoreClasses = ['text-sm-button', 'text-sm-input']
1578
+ const ignoreClasses = [
1579
+ 'text-button-size',
1580
+ 'text-input-size',
1581
+
1582
+ 'ring-button-primary-ring',
1583
+ 'ring-button-secondary-ring',
1584
+ 'ring-button-black-ring',
1585
+ 'ring-button-dark-ring',
1586
+ 'ring-button-white-ring',
1587
+ 'ring-button-clear-ring',
1588
+
1589
+ 'bg-button-dark',
1590
+ 'bg-button-dark-hover',
1591
+ ]
1565
1592
  const classes = args.filter(Boolean).join(' ').split(' ')
1566
1593
 
1567
1594
  const filteredClasses = classes.filter(c => {