nitro-web 0.0.73 → 0.0.75

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/app.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { createBrowserRouter, createHashRouter, redirect, useParams, RouterProvider } from 'react-router-dom'
1
+ import { createBrowserRouter, createHashRouter, redirect, RouterProvider } from 'react-router-dom'
2
2
  import { Fragment, ReactNode } from 'react'
3
3
  import ReactDOM from 'react-dom/client'
4
4
  import { axios, camelCase, pick, toArray, setTimeoutPromise } from 'nitro-web/util'
@@ -25,7 +25,7 @@ type Settings = {
25
25
  }
26
26
 
27
27
  type Route = {
28
- component: React.FC<{ route?: Route; params?: object; location?: object; config?: Config }>
28
+ component: React.FC<{ route?: Route; config?: Config }>
29
29
  middleware: string[]
30
30
  name: string
31
31
  path: string
@@ -70,9 +70,7 @@ export function updateJwt(token?: string | null) {
70
70
  }
71
71
 
72
72
  function App({ settings, config, storeContainer }: { settings: Settings, config: Config, storeContainer: StoreContainer }): ReactNode {
73
- // const themeNormalised = theme
74
- const router = getRouter({ settings, config })
75
- // const theme = pick(themeNormalised, []) // e.g. 'topPanelHeight'
73
+ const router = useMemo(() => getRouter({ settings, config }), [])
76
74
 
77
75
  useEffect(() => {
78
76
  /**
@@ -97,10 +95,8 @@ function App({ settings, config, storeContainer }: { settings: Settings, config:
97
95
 
98
96
  return (
99
97
  <storeContainer.Provider>
100
- {/* <ThemeProvider theme={themeNormalised}> */}
101
- { router && <RouterProvider router={router} /> }
98
+ { router && <RouterProvider router={router}/> }
102
99
  <AfterApp settings={settings} />
103
- {/* </ThemeProvider> */}
104
100
  </storeContainer.Provider>
105
101
  )
106
102
  }
@@ -206,7 +202,7 @@ function getRouter({ settings, config }: { settings: Settings, config: Config })
206
202
  ),
207
203
  path: route.path,
208
204
  loader: async () => { // request
209
- // wait for container/exposedStoreData to be setup
205
+ // wait for container/exposedStoreData to be setup (note that this causes ReactRouter to re-render, but not the page)
210
206
  if (!nonce) {
211
207
  nonce = true
212
208
  await setTimeoutPromise(() => {}, 0)
@@ -255,11 +251,9 @@ function RestoreScroll() {
255
251
 
256
252
  function RouteComponent({ route, config }: { route: Route, config: Config }) {
257
253
  const Component = route.component
258
- const params = useParams()
259
- const location = useLocation()
260
254
  document.title = route.meta?.title || ''
261
255
  return (
262
- <Component route={route} params={params} location={location} config={config} />
256
+ <Component route={route} config={config} />
263
257
  )
264
258
  }
265
259
 
@@ -106,7 +106,7 @@ async function store(req, res) {
106
106
  async function signup(req, res) {
107
107
  try {
108
108
  const desktop = req.query.desktop
109
- let user = await this.userCreate(req.body, this.findUserFromProvider)
109
+ let user = await this.userCreate(req.body)
110
110
  sendEmail({
111
111
  config: authConfig,
112
112
  template: 'welcome',
@@ -315,8 +315,12 @@ export async function signinAndGetStore(user, isDesktop, getStore) {
315
315
  return { ...store, jwt }
316
316
  }
317
317
 
318
- export async function userCreate({ name, business, email, password, findUserFromProvider }) {
318
+ export async function userCreate({ name, business, email, password }) {
319
319
  try {
320
+ if (!this.findUserFromProvider) {
321
+ throw new Error('this.findUserFromProvider doesn\'t exist, make sure the context is available when calling this function')
322
+ }
323
+
320
324
  const options = { blacklist: ['-_id'] }
321
325
  const isMultiTenant = !authConfig.isNotMultiTenant
322
326
  const userId = db.id()
@@ -14,14 +14,14 @@ type DropdownProps = {
14
14
  options?: { label: string|React.ReactNode, onClick?: Function, isSelected?: boolean, icon?: React.ReactNode, className?: string }[]
15
15
  /** Whether the dropdown is hoverable **/
16
16
  isHoverable?: boolean
17
- /** The minimum width of the menu **/
18
- minWidth?: number | string
19
17
  /** The content to render inside the top of the dropdown **/
20
18
  menuContent?: React.ReactNode
21
19
  menuClassName?: string
22
20
  menuOptionClassName?: string
23
21
  menuIsOpen?: boolean
24
22
  menuToggles?: boolean
23
+ /** The minimum width of the menu **/
24
+ minWidth?: number | string
25
25
  toggleCallback?: (isActive: boolean) => void
26
26
  }
27
27
 
@@ -30,15 +30,15 @@ export const Dropdown = forwardRef(function Dropdown({
30
30
  animate=true,
31
31
  children,
32
32
  className,
33
- dir,
33
+ dir='bottom-left',
34
34
  options,
35
35
  isHoverable,
36
- minWidth, // remove in favour of menuClassName
37
36
  menuClassName,
38
37
  menuOptionClassName,
39
38
  menuContent,
40
39
  menuIsOpen,
41
40
  menuToggles=true,
41
+ minWidth,
42
42
  toggleCallback,
43
43
  }: DropdownProps, ref) {
44
44
  // https://letsbuildui.dev/articles/building-a-dropdown-menu-component-with-react-hooks
@@ -46,6 +46,8 @@ export const Dropdown = forwardRef(function Dropdown({
46
46
  const dropdownRef = useRef<HTMLDivElement|null>(null)
47
47
  const [isActive, setIsActive] = useState(!!menuIsOpen)
48
48
  const menuStyle = getSelectStyle({ name: 'menu' })
49
+ const [direction, setDirection] = useState<null | 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'>(null)
50
+ const [ready, setReady] = useState(false)
49
51
 
50
52
  // Expose the setIsActive function to the parent component
51
53
  useImperativeHandle(ref, () => ({ setIsActive }))
@@ -76,7 +78,56 @@ export const Dropdown = forwardRef(function Dropdown({
76
78
  useEffect(() => {
77
79
  if (toggleCallback) toggleCallback(isActive)
78
80
  }, [isActive])
81
+
82
+ useEffect(() => {
83
+ setReady(false)
84
+ console.log(1111, isActive, dropdownRef.current)
85
+ if (!isActive || !dropdownRef.current) return
86
+
87
+ // const ul = dropdownRef.current.querySelector('&>ul') as HTMLElement
88
+ // if (!ul) return
89
+ // console.log(1111)
90
+
91
+ // Temporarily show the ul for measurement
92
+ // const originalMaxHeight = ul.style.maxHeight
93
+ // const originalVisibility = ul.style.visibility
94
+ // const originalOpacity = ul.style.opacity
95
+ // const originalPointerEvents = ul.style.pointerEvents
96
+
97
+ // ul.style.maxHeight = 'none'
98
+ // ul.style.visibility = 'hidden'
99
+ // ul.style.opacity = '0'
100
+ // ul.style.pointerEvents = 'none'
101
+
102
+ // const dropdownHeight = ul.getBoundingClientRect().height
79
103
 
104
+ // Revert styles
105
+ // ul.style.maxHeight = originalMaxHeight
106
+ // ul.style.visibility = originalVisibility
107
+ // ul.style.opacity = originalOpacity
108
+ // ul.style.pointerEvents = originalPointerEvents
109
+
110
+
111
+ // const rect = dropdownRef.current.getBoundingClientRect()
112
+ // const spaceBelow = window.innerHeight - rect.bottom
113
+ // const spaceAbove = rect.top
114
+
115
+ // const side = dir.endsWith('right') ? 'right' : 'left'
116
+
117
+ // const newDirection = dir.startsWith('bottom')
118
+ // ? `${1 < 1 && 1 > 1 ? 'top' : 'bottom'}-${side}`
119
+ // : `${1 < 1 && 1 > 1 ? 'bottom' : 'top'}-${side}`
120
+
121
+ // setDirection(dir as 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right')
122
+
123
+ // requestAnimationFrame(() => {
124
+ // // console.log('ul', originalOpacity)
125
+ // setReady(true)
126
+ // })
127
+ setDirection(dir)
128
+ setReady(true)
129
+ }, [isActive, dir])
130
+
80
131
  function onMouseDown(e: { key: string, preventDefault: Function }) {
81
132
  if (e.key && e.key != 'Enter') return
82
133
  if (e.key) e.preventDefault() // for button, stops buttons firing twice
@@ -91,8 +142,8 @@ export const Dropdown = forwardRef(function Dropdown({
91
142
  return (
92
143
  <div
93
144
  class={
94
- 'relative' +
95
- (dir ? ` is-${dir}` : ' is-bottom-left') +
145
+ `relative is-${direction || dir}` + // until hovered, show the original direction to prevent scrollbars
146
+ (ready ? ' is-ready' : '') +
96
147
  (isHoverable ? ' is-hoverable' : '') +
97
148
  (isActive ? ' is-active' : '') +
98
149
  (!animate ? ' no-animation' : '') +
@@ -113,7 +164,8 @@ export const Dropdown = forwardRef(function Dropdown({
113
164
  }
114
165
  <ul
115
166
  style={{ minWidth }}
116
- class={twMerge(`${menuStyle} absolute invisible opacity-0 select-none min-w-full z-[1] ${menuClassName}`)}
167
+ class={
168
+ twMerge(`${menuStyle} absolute invisible opacity-0 select-none min-w-full z-[1] ${menuClassName||''}`)}
117
169
  >
118
170
  {menuContent}
119
171
  {
@@ -146,7 +198,7 @@ const style = css`
146
198
  }
147
199
  &.is-bottom-right,
148
200
  &.is-top-right {
149
- ul {
201
+ &>ul {
150
202
  left: auto;
151
203
  right: 0;
152
204
  }
@@ -166,6 +218,7 @@ const style = css`
166
218
  }
167
219
  }
168
220
  // active submenu
221
+ &./////////////
169
222
  &.is-hoverable:hover,
170
223
  &:focus,
171
224
  &.is-active,
@@ -119,9 +119,10 @@ export const Filters = forwardRef<FiltersHandleType, FiltersProps>(({
119
119
 
120
120
  return (
121
121
  <Elements.Dropdown
122
+ // menuIsOpen={true}
123
+ className='ddddddddd'
122
124
  dir="bottom-right"
123
125
  allowOverflow={true}
124
- // menuIsOpen={true}
125
126
  {...dropdownProps}
126
127
  menuClassName={twMerge(`min-w-[330px] ${dropdownProps?.menuClassName || ''}`)}
127
128
  menuContent={
@@ -1,7 +1,7 @@
1
1
  import GithubIcon from 'nitro-web/client/imgs/github.svg'
2
2
 
3
3
  export function GithubLink({ filename }: { filename: string }) {
4
- const base = 'https://github.com/boycce/nitro-web/blob/master/'
4
+ const base = 'https://github.com/boycce/nitro-web/blob/master/packages/'
5
5
  // Filenames are relative to the webpack start directory
6
6
  // 1. Remove ../ from filename (i.e. for _example build)
7
7
  // 2. Remove node_modules/nitro-web/ from filename (i.e. for packages using nitro-web)
@@ -1,121 +1,146 @@
1
- import { twMerge } from 'nitro-web/util'
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { twMerge, deepFind, getErrorFromState } from 'nitro-web/util'
3
+ import { Errors, type Error } from 'nitro-web/types'
2
4
 
3
- type CheckboxProps = {
5
+ type CheckboxProps = React.InputHTMLAttributes<HTMLInputElement> & {
6
+ /** field name or path on state (used to match errors), e.g. 'date', 'company.email' */
4
7
  name: string
5
- /** name is applied if not provided. Used for radios */
8
+ /** name is applied if id is not provided. Used for radios */
6
9
  id?: string
7
- size?: 'md' | 'sm'
10
+ /** state object to get the value, and check errors against */
11
+ state?: { errors?: Errors, [key: string]: any }
12
+ size?: number
8
13
  subtext?: string|React.ReactNode
9
14
  text?: string|React.ReactNode
10
15
  type?: 'checkbox' | 'radio' | 'toggle'
11
- [key: string]: unknown
16
+ checkboxClassName?: string
17
+ svgClassName?: string
18
+ labelClassName?: string
12
19
  }
13
20
 
14
- export function Checkbox({ name, id, size='sm', subtext, text, type='checkbox', ...props }: CheckboxProps) {
21
+ export function Checkbox({
22
+ state, size, subtext, text, type='checkbox', className, checkboxClassName, svgClassName, labelClassName, ...props
23
+ }: CheckboxProps) {
15
24
  // Checkbox/radio/toggle component
16
- // https://tailwindui.com/components/application-ui/forms/checkboxes#component-744ed4fa65ba36b925701eb4da5c6e31
17
- if (!name) throw new Error('Checkbox requires a `name` prop')
18
- id = id || name
25
+ let value!: boolean
26
+ const error = getErrorFromState(state, props.name)
27
+ const id = props.id || props.name
19
28
 
20
- const sizeMap = {
21
- sm: {
22
- checkbox: 'size-[14px]',
23
- toggleWidth: 'w-[32px]', // 4px border + (toggleAfterSize * 2)
24
- toggleHeight: 'h-[18px]',
25
- toggleAfterSize: 'after:size-[14px]', // account for 2px border
26
- },
27
- md: {
28
- checkbox: 'size-[16px]',
29
- toggleWidth: 'w-[40px]', // 4px border + (toggleAfterSize * 2)
30
- toggleHeight: 'h-[22px]',
31
- toggleAfterSize: 'after:size-[18px]', // account for 2px border
32
- },
29
+ if (!props.name) throw new Error('Checkbox requires a `name` prop')
30
+
31
+ // Value: Input is always controlled if state is passed in
32
+ if (typeof props.checked !== 'undefined') value = props.checked
33
+ else if (typeof state == 'object') {
34
+ const v = deepFind(state, props.name) as boolean | undefined
35
+ value = v ?? false
33
36
  }
34
- const _size = sizeMap[size]
37
+
38
+ const BORDER = 2
39
+ const checkboxSize = size ?? 14
40
+ const toggleHeight = size ?? 18
41
+ const toggleWidth = toggleHeight * 2 - BORDER * 2
42
+ const toggleAfterSize = toggleHeight - BORDER * 2
35
43
 
36
44
  return (
37
- <div className={'mt-2.5 mb-6 ' + twMerge(`mt-input-before mb-input-after flex gap-3 nitro-checkbox ${props.className || ''}`)}>
38
- <div className="flex shrink-0 mt-[2px]">
39
- {
40
- type !== 'toggle'
41
- ? <div className={`group grid ${_size.checkbox} grid-cols-1`}>
42
- <input
43
- {...props}
44
- id={id}
45
- name={name}
46
- type={type}
47
- className={
48
- `${type === 'radio' ? 'rounded-full' : 'rounded'} col-start-1 row-start-1 appearance-none border border-gray-300 bg-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 disabled:border-gray-300 disabled:bg-gray-100 disabled:checked:bg-gray-100 forced-colors:appearance-auto ` +
49
- // Default
50
- 'checked:border-blue-600 checked:bg-blue-600 indeterminate:border-blue-600 indeterminate:bg-blue-600 focus-visible:outline-blue-600 ' +
51
- // Variable-selected color defined?
52
- 'checked:!border-variable-selected checked:!bg-variable-selected indeterminate:!border-variable-selected indeterminate:!bg-variable-selected focus-visible:!outline-variable-selected'
53
- }
54
- />
55
- <svg
56
- fill="none"
57
- viewBox="0 0 14 14"
58
- className={`pointer-events-none col-start-1 row-start-1 ${_size.checkbox} self-center justify-self-center stroke-white group-has-[:disabled]:stroke-gray-950/25`}
59
- >
60
- {
61
- type === 'radio'
62
- ? <circle
63
- // cx={(_size.checkbox.match(/\d+/)?.[0] as unknown as number) / 2}
64
- // cy={(_size.checkbox.match(/\d+/)?.[0] as unknown as number) / 2}
65
- // r={(_size.checkbox.match(/\d+/)?.[0] as unknown as number) / 6}
66
- cx={7}
67
- cy={7}
68
- r={2.5}
69
- className="fill-white opacity-0 group-has-[:checked]:opacity-100"
70
- />
71
- : <>
72
- <path
73
- d="M4 8L6 10L10 4.5"
74
- strokeWidth={2}
75
- strokeLinecap="round"
76
- strokeLinejoin="round"
77
- className="opacity-0 group-has-[:checked]:opacity-100"
78
- />
79
- <path
80
- d="M4 7H10"
81
- strokeWidth={2}
82
- strokeLinecap="round"
83
- strokeLinejoin="round"
84
- className="opacity-0 group-has-[:indeterminate]:opacity-100"
85
- />
86
- </>
87
- }
88
- </svg>
89
- </div>
90
- : <div className="group grid grid-cols-1">
91
- <input
92
- {...props}
93
- id={id}
94
- name={name}
95
- type="checkbox"
96
- class="sr-only peer"
97
- />
98
- <label
99
- for={id}
100
- className={
101
- `col-start-1 row-start-1 relative ${_size.toggleWidth} ${_size.toggleHeight} bg-gray-200 peer-focus-visible:outline-none peer-focus-visible:ring-4 rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full ${_size.toggleAfterSize} after:transition-all ` +
102
- // Default
103
- 'peer-focus-visible:ring-blue-300 peer-checked:bg-blue-600 ' +
104
- // Variable-selected color defined?
105
- 'peer-focus-visible:!ring-variable-selected peer-checked:!bg-variable-selected '
106
- // Dark mode not used yet...
107
- // 'dark:peer-focus-visible:ring-blue-800 dark:bg-gray-700 dark:border-gray-600 '
108
- }
109
- />
110
- </div>
45
+ <div
46
+ className={'mt-2.5 mb-6 ' + twMerge(`mt-input-before mb-input-after text-sm nitro-checkbox ${className}`)}
47
+ >
48
+ <div className="flex gap-3 items-baseline">
49
+ <div className="shrink-0 flex items-center">
50
+ <div className="w-0">&nbsp;</div>
51
+ <div className="group relative">
52
+ {
53
+ type !== 'toggle'
54
+ ? <>
55
+ <input
56
+ {...props}
57
+ type={type}
58
+ style={{ width: checkboxSize, height: checkboxSize }}
59
+ checked={value}
60
+ className={
61
+ twMerge(
62
+ `${type === 'radio' ? 'rounded-full' : 'rounded'} appearance-none border border-gray-300 bg-white forced-colors:appearance-auto disabled:border-gray-300 disabled:bg-gray-100 disabled:checked:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 ` +
63
+ // Variable-selected theme colors (was .*-blue-600)
64
+ 'checked:border-variable-selected checked:bg-variable-selected indeterminate:border-variable-selected indeterminate:bg-variable-selected focus-visible:outline-variable-selected ' +
65
+ // Dark mode not used yet... dark:focus-visible:outline-blue-800
66
+ checkboxClassName
67
+ )
68
+ }
69
+ />
70
+ <svg
71
+ fill="none"
72
+ viewBox="0 0 14 14"
73
+ style={{ width: checkboxSize, height: checkboxSize }}
74
+ className={twMerge('absolute top-0 left-0 pointer-events-none justify-self-center stroke-white group-has-[:disabled]:stroke-gray-950/25', svgClassName)}
75
+ >
76
+ {
77
+ type === 'radio'
78
+ ? <circle
79
+ // cx={(_size.checkbox.match(/\d+/)?.[0] as unknown as number) / 2}
80
+ // cy={(_size.checkbox.match(/\d+/)?.[0] as unknown as number) / 2}
81
+ // r={(_size.checkbox.match(/\d+/)?.[0] as unknown as number) / 6}
82
+ cx={7}
83
+ cy={7}
84
+ r={2.5}
85
+ className="fill-white opacity-0 group-has-[:checked]:opacity-100"
86
+ />
87
+ : <>
88
+ <path
89
+ d="M4 8L6 10L10 4.5"
90
+ strokeWidth={2}
91
+ strokeLinecap="round"
92
+ strokeLinejoin="round"
93
+ className="opacity-0 group-has-[:checked]:opacity-100"
94
+ />
95
+ <path
96
+ d="M4 7H10"
97
+ strokeWidth={2}
98
+ strokeLinecap="round"
99
+ strokeLinejoin="round"
100
+ className="opacity-0 group-has-[:indeterminate]:opacity-100"
101
+ />
102
+ </>
103
+ }
104
+ </svg>
105
+ </>
106
+ : <>
107
+ <input
108
+ {...props}
109
+ type="checkbox"
110
+ className="sr-only peer"
111
+ checked={value}
112
+ />
113
+ <label
114
+ for={id}
115
+ style={{ width: toggleWidth, height: toggleHeight }}
116
+ className={
117
+ twMerge(
118
+ 'block bg-gray-200 rounded-full transition-colors peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 ' +
119
+ // Variable-selected theme colors (was .*-blue-600)
120
+ 'peer-checked:bg-variable-selected peer-focus-visible:outline-variable-selected ' +
121
+ labelClassName
122
+ )
123
+ }
124
+ >
125
+ <span
126
+ style={{ width: toggleAfterSize, height: toggleAfterSize }}
127
+ className={
128
+ 'absolute top-[2px] start-[2px] bg-white border-gray-300 border rounded-full transition-all group-has-[:checked]:border-white group-has-[:checked]:translate-x-full '
129
+ }
130
+ />
131
+ </label>
132
+ </>
133
+ }
134
+ </div>
135
+ </div>
136
+ {text &&
137
+ <label for={id} className="text-[length:inherit] leading-[inherit] select-none">
138
+ <span className="text-gray-900">{text}</span>
139
+ <span className="ml-2 text-gray-500">{subtext}</span>
140
+ </label>
111
141
  }
112
142
  </div>
113
- {text &&
114
- <label for={id} className="self-center text-sm select-none">
115
- <span className="text-gray-900">{text}</span>
116
- <span className="ml-2 text-gray-500">{subtext}</span>
117
- </label>
118
- }
143
+ {error && <div class="mt-1.5 text-xs text-danger-foreground nitro-error">{error.detail}</div>}
119
144
  </div>
120
145
  )
121
146
  }
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- // Maybe use fill-current tw class for lucide icons (https://github.com/lucide-icons/lucide/discussions/458)
2
+ // fill-current tw class for lucide icons (https://github.com/lucide-icons/lucide/discussions/458)
3
3
  import { css } from 'twin.macro'
4
4
  import { FieldCurrency, FieldCurrencyProps, FieldColor, FieldColorProps, FieldDate, FieldDateProps } from 'nitro-web'
5
5
  import { twMerge, getErrorFromState, deepFind } from 'nitro-web/util'
@@ -22,7 +22,7 @@ type FieldExtraProps = {
22
22
  /** icon to show in the input */
23
23
  icon?: React.ReactNode
24
24
  iconPos?: 'left' | 'right'
25
- /** Pass dependencies to break memoization, handy for onChange/onInputChange */
25
+ /** Dependencies to break the implicit memoization of onChange/onInputChange */
26
26
  deps?: unknown[]
27
27
  placeholder?: string
28
28
  }
@@ -181,25 +181,19 @@ function ColorSvg({ hex }: { hex?: string }) {
181
181
  }
182
182
 
183
183
  export function isFieldCached(prev: IsFieldCachedProps, next: IsFieldCachedProps) {
184
+ // Check if the field is cached, onChange/onInputChange doesn't affect the cache
184
185
  const path = prev.name
185
186
  const state = prev.state || {}
186
- // If state/satte-error values have changed, re-render!
187
- if (deepFind(state, path) !== deepFind(next.state || {}, path)) {
188
- // console.log(1, 'state changed', path)
189
- return false
190
- }
191
- if (getErrorFromState(state, path) !== getErrorFromState(next.state || {}, path)) {
192
- // console.log(2, 'error changed', path)
193
- return false
194
- }
195
- // If deps have changed, re-render!
196
- if ((next.deps?.length !== prev.deps?.length) || next.deps?.some((v, i) => v !== prev.deps?.[i])) {
197
- // console.log(3, 'deps changed', path)
198
- return false
199
- }
200
- // If any other props have changed, except onChange/onInputChange, re-render!
201
- // In most cases, onChange/onInputChange remain the same and reference the same function...
202
- for (const k in prev) {
187
+ // If the state value has changed, re-render!
188
+ if (deepFind(state, path) !== deepFind(next.state || {}, path)) return false
189
+ // If the state error has changed, re-render!
190
+ if (getErrorFromState(state, path) !== getErrorFromState(next.state || {}, path)) return false
191
+ // If `deps` have changed, handy for onChange/onInputChange, re-render!
192
+ if ((next.deps?.length !== prev.deps?.length) || next.deps?.some((v, i) => v !== prev.deps?.[i])) return false
193
+
194
+ // Check if any prop has changed, except `onChange`/`onInputChange`
195
+ const allKeys = new Set([...Object.keys(prev), ...Object.keys(next)])
196
+ for (const k of allKeys) {
203
197
  if (k === 'state' || k === 'onChange' || k === 'onInputChange') continue
204
198
  if (prev[k as keyof typeof prev] !== next[k as keyof typeof next]) {
205
199
  // console.log(4, 'changed', path, k)
@@ -204,18 +204,21 @@ export function Styleguide({ className, elements, children }: StyleguideProps) {
204
204
  <div class="grid grid-cols-3 gap-x-6 mb-4">
205
205
  <div>
206
206
  <label for="input2">Label</label>
207
- <Checkbox name="input2" type="toggle" text="Toggle sm" subtext="some additional text here." class="!mb-0" defaultChecked />
208
- <Checkbox name="input3" type="toggle" text="Toggle md" size="md" subtext="some additional text here." />
207
+ <Checkbox name="input2" type="toggle" text="Toggle sm" subtext="some additional text here." class="!mb-0"
208
+ state={state} onChange={(e) => onChange(setState, e)} />
209
+ <Checkbox name="input3" type="toggle" text="Toggle 22px" subtext="some additional text here." size={22} />
209
210
  </div>
210
211
  <div>
211
212
  <label for="input1">Label</label>
212
- <Checkbox name="input1" type="radio" text="Radio 1" subtext="some additional text here 1." id="input1-1" class="!mb-0"
213
+ <Checkbox name="input1" type="radio" text="Radio" subtext="some additional text here 1." id="input1-1" class="!mb-0"
213
214
  defaultChecked />
214
- <Checkbox name="input1" type="radio" text="Radio 2" subtext="some additional text here 2." id="input1-2" class="!mt-0" />
215
+ <Checkbox name="input1" type="radio" text="Radio 16px" subtext="some additional text here 2." id="input1-2" size={16} />
215
216
  </div>
216
217
  <div>
217
218
  <label for="input0">Label</label>
218
- <Checkbox name="input0" type="checkbox" text="Checkbox" subtext="some additional text here." defaultChecked />
219
+ <Checkbox name="input0" type="checkbox" text="Checkbox" subtext="some additional text here." class="!mb-0" defaultChecked />
220
+ <Checkbox name="input0.1" type="checkbox" text="Checkbox 16px" size={16}
221
+ subtext="some additional text here which is a bit longer that will be line-wrap to the next line." />
219
222
  </div>
220
223
  </div>
221
224
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitro-web",
3
- "version": "0.0.73",
3
+ "version": "0.0.75",
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 🚀",