nitro-web 0.0.107 โ†’ 0.0.109

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.
@@ -4,9 +4,10 @@ import { Errors } from 'nitro-web/types'
4
4
  type resetInstructionsProps = {
5
5
  className?: string,
6
6
  elements?: { Button?: typeof Button },
7
+ redirectTo?: string,
7
8
  }
8
9
 
9
- export function ResetInstructions({ className, elements }: resetInstructionsProps) {
10
+ export function ResetInstructions({ className, elements, redirectTo }: resetInstructionsProps) {
10
11
  const navigate = useNavigate()
11
12
  const isLoading = useState(false)
12
13
  const [, setStore] = useTracked()
@@ -20,7 +21,7 @@ export function ResetInstructions({ className, elements }: resetInstructionsProp
20
21
  try {
21
22
  await request('post /api/reset-instructions', state, event, isLoading, setState)
22
23
  setStore((s) => ({ ...s, message: 'Done! Please check your email.' }))
23
- navigate('/signin')
24
+ navigate(redirectTo || '/signin')
24
25
  } catch (e) {
25
26
  return setState({ ...state, errors: e as Errors })
26
27
  }
@@ -47,7 +48,7 @@ export function ResetInstructions({ className, elements }: resetInstructionsProp
47
48
  )
48
49
  }
49
50
 
50
- export function ResetPassword({ className, elements }: resetInstructionsProps) {
51
+ export function ResetPassword({ className, elements, redirectTo }: resetInstructionsProps) {
51
52
  const navigate = useNavigate()
52
53
  const params = useParams()
53
54
  const isLoading = useState(false)
@@ -67,7 +68,7 @@ export function ResetPassword({ className, elements }: resetInstructionsProps) {
67
68
  try {
68
69
  const data = await request('post /api/reset-password', state, event, isLoading, setState)
69
70
  setStore((s) => ({ ...s, ...data }))
70
- navigate('/')
71
+ navigate(redirectTo || '/')
71
72
  } catch (e) {
72
73
  return setState({ ...state, errors: e as Errors })
73
74
  }
@@ -4,9 +4,10 @@ import { Errors } from 'nitro-web/types'
4
4
  type signinProps = {
5
5
  className?: string,
6
6
  elements?: { Button?: typeof Button },
7
+ redirectTo?: string,
7
8
  }
8
9
 
9
- export function Signin({ className, elements }: signinProps) {
10
+ export function Signin({ className, elements, redirectTo }: signinProps) {
10
11
  const navigate = useNavigate()
11
12
  const location = useLocation()
12
13
  const isSignout = location.pathname == '/signout'
@@ -48,7 +49,7 @@ export function Signin({ className, elements }: signinProps) {
48
49
  setStore((s) => ({ ...s, ...data }))
49
50
  setTimeout(() => { // wait for setStore
50
51
  if (location.search.includes('redirect')) navigate(location.search.replace('?redirect=', ''))
51
- else navigate('/')
52
+ else navigate(redirectTo || '/')
52
53
  }, 100)
53
54
  } catch (e) {
54
55
  return setState({ ...state, errors: e as Errors})
@@ -4,9 +4,10 @@ import { Errors } from 'nitro-web/types'
4
4
  type signupProps = {
5
5
  className?: string,
6
6
  elements?: { Button?: typeof Button },
7
+ redirectTo?: string,
7
8
  }
8
9
 
9
- export function Signup({ className, elements }: signupProps) {
10
+ export function Signup({ className, elements, redirectTo }: signupProps) {
10
11
  const navigate = useNavigate()
11
12
  const isLoading = useState(false)
12
13
  const [, setStore] = useTracked()
@@ -26,7 +27,7 @@ export function Signup({ className, elements }: signupProps) {
26
27
  try {
27
28
  const data = await request('post /api/signup', state, e, isLoading, setState)
28
29
  setStore((prev) => ({ ...prev, ...data }))
29
- setTimeout(() => navigate('/'), 0) // wait for setStore
30
+ setTimeout(() => navigate(redirectTo || '/'), 0) // wait for setStore
30
31
  } catch (e) {
31
32
  setState((prev) => ({ ...prev, errors: e as Errors }))
32
33
  }
@@ -1,4 +1,4 @@
1
- import { DayPicker, getDefaultClassNames } from 'react-day-picker'
1
+ import { DayPicker, getDefaultClassNames, DayPickerProps as DayPickerPropsBase } from 'react-day-picker'
2
2
  import { isValid } from 'date-fns'
3
3
  import 'react-day-picker/style.css'
4
4
  import { IsFirstRender } from 'nitro-web'
@@ -11,8 +11,10 @@ type ModeSelection<T extends Mode> = (
11
11
  : T extends 'multiple' ? Date[]
12
12
  : { from?: Date; to?: Date }
13
13
  )
14
+ export type DayPickerProps = Omit<DayPickerPropsBase,
15
+ 'mode' | 'selected' | 'onSelect' | 'modifiersClassNames' | 'classNames' | 'numberOfMonths' | 'month' | 'onMonthChange'>
14
16
 
15
- export type CalendarProps = {
17
+ export type CalendarProps = DayPickerProps & {
16
18
  mode?: Mode
17
19
  onChange?: (mode: Mode, value: null|number|(null|number)[]) => void
18
20
  value?: null|number|string|(null|number|string)[]
@@ -22,7 +24,8 @@ export type CalendarProps = {
22
24
  preserveTime?: boolean // just for single mode
23
25
  }
24
26
 
25
- export function Calendar({ mode='single', onChange, value, numberOfMonths, month: monthProp, className, preserveTime }: CalendarProps) {
27
+ export function Calendar({ mode='single', onChange, value, numberOfMonths, month: monthProp, className, preserveTime,
28
+ ...props }: CalendarProps) {
26
29
  const isFirstRender = IsFirstRender()
27
30
  const isRange = mode == 'range'
28
31
 
@@ -113,11 +116,11 @@ export function Calendar({ mode='single', onChange, value, numberOfMonths, month
113
116
  <div>
114
117
  {
115
118
  mode === 'single' ? (
116
- <DayPicker mode="single" selected={dates[0]} {...common} className={className} />
119
+ <DayPicker {...props} {...common} mode="single" selected={dates[0]} className={className} />
117
120
  ) : mode === 'range' ? (
118
- <DayPicker mode="range" selected={{ from: dates[0], to: dates[1] }} {...common} className={className} />
121
+ <DayPicker {...props} {...common} mode="range" selected={{ from: dates[0], to: dates[1] }} className={className} />
119
122
  ) : (
120
- <DayPicker mode="multiple" selected={dates.filter((d) => !!d)} {...common} className={className} />
123
+ <DayPicker {...props} {...common} mode="multiple" selected={dates.filter((d) => !!d)} className={className} />
121
124
  )
122
125
  }
123
126
  </div>
@@ -100,8 +100,8 @@ export function Table<T extends TableRow>({
100
100
  else return align == 'left' ? '' : align == 'center' ? 'text-center' : 'text-right'
101
101
  }, [])
102
102
 
103
- // Reset selected rows when the location changes
104
- useEffect(() => setSelectedRowIds([]), [location.key])
103
+ // Reset selected rows when the location changes, or the number of rows changed (e.g. when a row is removed)
104
+ useEffect(() => setSelectedRowIds([]), [location.key, rows.map(row => row?._id||'').join(',')])
105
105
 
106
106
  // --- Sorting ---
107
107
 
@@ -134,7 +134,8 @@ export function Table<T extends TableRow>({
134
134
  columns.map((col, j) => {
135
135
  const disableSort = col.disableSort || selectedRowIds.length
136
136
  const sideColor = j == 0 && rowSideColor ? rowSideColor(undefined) : undefined
137
- const pl = j == 0 ? columnPaddingX : columnGap
137
+ const sideColorPadding = sideColor && rows.length > 0 ? sideColor.width + 5 : 0
138
+ const pl = sideColorPadding + (j == 0 ? columnPaddingX : columnGap)
138
139
  const pr = j == columns.length - 1 ? columnPaddingX : columnGap
139
140
  return (
140
141
  <div
@@ -152,13 +153,10 @@ export function Table<T extends TableRow>({
152
153
  )}
153
154
  >
154
155
  <div
155
- style={{
156
- maxHeight: rowContentHeightMax,
157
- paddingLeft: sideColor && rows.length > 0 ? sideColor.width + 5 : 0,
158
- }}
156
+ style={{ maxHeight: rowContentHeightMax }}
159
157
  className={twMerge(
158
+ col.value == 'checkbox' ? 'relative' : getLineClampClassName(col.rowLinesMax),
160
159
  rowContentHeightMax ? 'overflow-hidden' : '',
161
- getLineClampClassName(col.rowLinesMax),
162
160
  col.overflow ? 'overflow-visible' : '',
163
161
  col.innerClassName
164
162
  )}
@@ -175,7 +173,7 @@ export function Table<T extends TableRow>({
175
173
  onChange={(e) => onSelect('all', e.target.checked)}
176
174
  />
177
175
  <div
178
- className={`${selectedRowIds.length ? 'block' : 'hidden'} [&>*]:absolute [&>*]:inset-y-0 [&>*]:left-[68px] [&>*]:z-10`}
176
+ className={`${selectedRowIds.length ? 'block' : 'hidden'} [&>*]:absolute [&>*]:inset-y-0 [&>*]:left-[35px] [&>*]:z-10 whitespace-nowrap`}
179
177
  >
180
178
  {generateCheckboxActions && generateCheckboxActions(selectedRowIds)}
181
179
  </div>
@@ -18,21 +18,20 @@ type NumericFormatProps = React.InputHTMLAttributes<HTMLInputElement> & {
18
18
  export type FieldCurrencyProps = NumericFormatProps & {
19
19
  name: string
20
20
  /** name is applied if id is not provided */
21
- id?: string
22
- /** e.g. { currencies: { nzd: { symbol: '$', digits: 2 } } } (check out the nitro example for more info) */
23
- config: {
24
- currencies: { [key: string]: { symbol: string, digits: number } },
25
- countries: { [key: string]: { numberFormats: { currency: string } } }
26
- }
21
+ id?: string
27
22
  /** currency iso, e.g. 'nzd' */
28
23
  currency: string
24
+ /** override the default currencies array used to lookup currency symbol and digits, e.g. {nzd: { symbol: '$', digits: 2 }} */
25
+ currencies?: { [key: string]: { symbol: string, digits: number } },
26
+ /** override the default CLDR country currency format, e.g. 'ยค#,##0.00' */
27
+ format?: string,
29
28
  onChange?: (event: { target: { name: string, value: string|number|null } }) => void
30
29
  /** value should be in cents */
31
30
  value?: string|number|null
32
31
  defaultValue?: number | string | null
33
32
  }
34
33
 
35
- export function FieldCurrency({ config, currency='nzd', onChange, value, defaultValue, ...props }: FieldCurrencyProps) {
34
+ export function FieldCurrency({ currency='nzd', currencies, format, onChange, value, defaultValue, ...props }: FieldCurrencyProps) {
36
35
  const [dontFix, setDontFix] = useState(false)
37
36
  const [settings, setSettings] = useState(() => getCurrencySettings(currency))
38
37
  const [dollars, setDollars] = useState(() => toDollars(value, true, settings))
@@ -61,7 +60,7 @@ export function FieldCurrency({ config, currency='nzd', onChange, value, default
61
60
 
62
61
  useEffect(() => {
63
62
  // Get the prefix content width
64
- setPrefixWidth(settings.prefix == '$' ? getPrefixWidth(settings.prefix, 1) : 0)
63
+ setPrefixWidth(settings.prefix ? getPrefixWidth(settings.prefix, 1) : 0)
65
64
  }, [settings.prefix])
66
65
 
67
66
  function toCents(value?: string|number|null) {
@@ -95,29 +94,33 @@ export function FieldCurrency({ config, currency='nzd', onChange, value, default
95
94
  prefix?: string, // e.g. '$'
96
95
  suffix?: string // e.g. ''
97
96
  } = { currency }
98
- const { symbol, digits } = config.currencies[currency]
99
- let format = config.countries['nz'].numberFormats.currency
97
+
98
+ let _format = format || defaultFormat
99
+ const _currencies = currencies ?? defaultCurrencies
100
+ const currencyObject = _currencies[currency as keyof typeof _currencies]
101
+ const symbol = currencyObject ? currencyObject.symbol : ''
102
+ const digits = currencyObject ? currencyObject.digits : 2
100
103
 
101
104
  // Check for currency symbol (ยค) and determine its position
102
- if (format.indexOf('ยค') !== -1) {
103
- const position = format.indexOf('ยค') === 0 ? 'prefix' : 'suffix'
105
+ if (_format.indexOf('ยค') !== -1) {
106
+ const position = _format.indexOf('ยค') === 0 ? 'prefix' : 'suffix'
104
107
  output[position] = symbol
105
- format = format.replace('ยค', '')
108
+ _format = _format.replace('ยค', '')
106
109
  }
107
110
 
108
111
  // Find and set the thousands separator
109
- const thousandMatch = format.match(/[^0-9#]/)
112
+ const thousandMatch = _format.match(/[^0-9#]/)
110
113
  if (thousandMatch) output.thousandSeparator = thousandMatch[0]
111
114
 
112
115
  // Find and set the decimal separator and fraction digits
113
- const decimalMatch = format.match(/0[^0-9]/)
116
+ const decimalMatch = _format.match(/0[^0-9]/)
114
117
  if (decimalMatch) {
115
118
  output.decimalSeparator = decimalMatch[0].slice(1)
116
119
  if (typeof digits !== 'undefined') {
117
120
  output.minDecimals = digits
118
121
  output.maxDecimals = digits
119
122
  } else {
120
- const fractionDigits = format.split(output.decimalSeparator)[1]
123
+ const fractionDigits = _format.split(output.decimalSeparator)[1]
121
124
  if (fractionDigits) {
122
125
  output.minDecimals = fractionDigits.length
123
126
  output.maxDecimals = fractionDigits.length
@@ -156,3 +159,14 @@ export function FieldCurrency({ config, currency='nzd', onChange, value, default
156
159
  </div>
157
160
  )
158
161
  }
162
+
163
+ const defaultCurrencies = {
164
+ nzd: { symbol: '$', digits: 2 },
165
+ aud: { symbol: '$', digits: 2 },
166
+ usd: { symbol: '$', digits: 2 },
167
+ eur: { symbol: 'โ‚ฌ', digits: 2 },
168
+ gbp: { symbol: 'ยฃ', digits: 2 },
169
+ btc: { symbol: 'โ‚ฟ', digits: 8 },
170
+ }
171
+
172
+ const defaultFormat = 'ยค#,##0.00'
@@ -2,7 +2,7 @@
2
2
  import { format, isValid, parse } from 'date-fns'
3
3
  import { getPrefixWidth } from 'nitro-web/util'
4
4
  import { Calendar, Dropdown } from 'nitro-web'
5
- import { dayButtonClassName } from '../element/calendar'
5
+ import { dayButtonClassName, DayPickerProps } from '../element/calendar'
6
6
 
7
7
  type Mode = 'single' | 'multiple' | 'range'
8
8
  type DropdownRef = {
@@ -26,6 +26,8 @@ type PreFieldDateProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onCh
26
26
  Icon?: React.ReactNode
27
27
  /** direction of the dropdown */
28
28
  dir?: 'bottom-left'|'bottom-right'|'top-left'|'top-right'
29
+ /** Calendar props */
30
+ DayPickerProps?: DayPickerProps
29
31
  }
30
32
 
31
33
  // An array is returned for mode = 'multiple' or 'range'
@@ -54,6 +56,7 @@ export function FieldDate({
54
56
  prefix = '',
55
57
  showTime,
56
58
  value: valueProp,
59
+ DayPickerProps,
57
60
  ...props
58
61
  }: FieldDateProps) {
59
62
  const localePattern = `d MMM yyyy${showTime && mode == 'single' ? ' hh:mmaa' : ''}`
@@ -146,10 +149,11 @@ export function FieldDate({
146
149
  <div className="flex">
147
150
  <Calendar
148
151
  // Calendar actually accepts an array of dates, but the type is not typed correctly
149
- {...{ mode: mode, value: dates as any, numberOfMonths: numberOfMonths, month: month }}
152
+ {...{ mode: mode, value: dates as any, numberOfMonths: numberOfMonths, month: month }}
153
+ {...DayPickerProps}
150
154
  preserveTime={!!showTime}
151
155
  onChange={onCalendarChange}
152
- className="pt-1 pb-2 px-3"
156
+ className="pt-1 pb-2 px-3"
153
157
  />
154
158
  {!!showTime && mode == 'single' && <TimePicker date={dates?.[0]} onChange={onCalendarChange} />}
155
159
  </div>
@@ -18,6 +18,7 @@ type StyleguideProps = {
18
18
  className?: string
19
19
  elements?: { Button?: typeof ButtonNitro }
20
20
  children?: React.ReactNode
21
+ currencies?: { [key: string]: { name: string, symbol: string, digits: number } }
21
22
  }
22
23
 
23
24
  type QuoteExample = {
@@ -29,7 +30,7 @@ type QuoteExample = {
29
30
  status: string
30
31
  }
31
32
 
32
- export function Styleguide({ className, elements, children }: StyleguideProps) {
33
+ export function Styleguide({ className, elements, children, currencies }: StyleguideProps) {
33
34
  const Button = elements?.Button || ButtonNitro
34
35
  const [, setStore] = useTracked()
35
36
  const [customerSearch, setCustomerSearch] = useState('')
@@ -40,7 +41,7 @@ export function Styleguide({ className, elements, children }: StyleguideProps) {
40
41
  brandColor: '#F3CA5F',
41
42
  colorsMulti: ['blue', 'green'],
42
43
  country: 'nz',
43
- currency: 'nzd', // can be commented too
44
+ currency: 'nzd',
44
45
  date: Date.now(),
45
46
  'date-range': [Date.now(), Date.now() + 1000 * 60 * 60 * 24 * 33],
46
47
  'date-time': Date.now(),
@@ -351,7 +352,7 @@ export function Styleguide({ className, elements, children }: StyleguideProps) {
351
352
  name="country"
352
353
  mode="country"
353
354
  state={state}
354
- options={useMemo(() => getCountryOptions(injectedConfig.countries), [])}
355
+ options={useMemo(() => [{ value: 'nz', label: 'New Zealand' }, { value: 'au', label: 'Australia' }], [])}
355
356
  onChange={(e) => onChange(setState, e)}
356
357
  />
357
358
  </div>
@@ -384,11 +385,11 @@ export function Styleguide({ className, elements, children }: StyleguideProps) {
384
385
  />
385
386
  </div>
386
387
  <div>
387
- <label for="currency">Currencies (Error)</label>
388
+ <label for="currency">Currencies</label>
388
389
  <Select
389
390
  name="currency"
390
391
  state={state}
391
- options={useMemo(() => getCurrencyOptions(injectedConfig.currencies), [])}
392
+ options={useMemo(() => (currencies ? getCurrencyOptions(currencies) : [{ value: 'nzd', label: 'New Zealand Dollar' }, { value: 'aud', label: 'Australian Dollar' }]), [])}
392
393
  onChange={(e) => onChange(setState, e)}
393
394
  />
394
395
  </div>
@@ -433,8 +434,12 @@ export function Styleguide({ className, elements, children }: StyleguideProps) {
433
434
  </div>
434
435
  <div>
435
436
  <label for="amount">Amount ({state.amount})</label>
436
- <Field name="amount" type="currency" state={state} currency={state.currency || 'nzd'} onChange={(e) => onChange(setState, e)}
437
- config={injectedConfig} />
437
+ <Field
438
+ name="amount" type="currency" state={state} currency={state.currency || 'nzd'} onChange={(e) => onChange(setState, e)}
439
+ // Example of using a custom format and currencies, e.g.
440
+ format={'ยค#,##0.00'}
441
+ currencies={currencies}
442
+ />
438
443
  </div>
439
444
  </div>
440
445
 
@@ -445,8 +450,18 @@ export function Styleguide({ className, elements, children }: StyleguideProps) {
445
450
  <Field name="date-time" type="date" mode="single" showTime={true} state={state} onChange={(e) => onChange(setState, e)} />
446
451
  </div>
447
452
  <div>
448
- <label for="date-range">Date range with prefix</label>
449
- <Field name="date-range" type="date" mode="range" prefix="Date:" state={state} onChange={(e) => onChange(setState, e)} />
453
+ <label for="date-range">Date range (with prefix & disabled days)</label>
454
+ <Field
455
+ name="date-range"
456
+ type="date"
457
+ mode="range"
458
+ prefix="Date:"
459
+ state={state}
460
+ onChange={(e) => onChange(setState, e)}
461
+ DayPickerProps={{
462
+ disabled: { after: new Date(Date.now() + 1000 * 60 * 60 * 24 * 45) }
463
+ }}
464
+ />
450
465
  </div>
451
466
  <div>
452
467
  <label for="date">Date multi-select (right aligned)</label>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitro-web",
3
- "version": "0.0.107",
3
+ "version": "0.0.109",
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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Returns an address monastery schema which Google autocomplete should return
2
+ * Returns a monastery schema which matches the Google autocomplete output
3
3
  */
4
4
  export function addressSchema(): {
5
5
  city: {
package/types.ts CHANGED
@@ -3,8 +3,6 @@
3
3
  type InjectedConfig = {
4
4
  awsUrl?: string
5
5
  clientUrl: string
6
- countries: { [key: string]: { name: string, numberFormats: { currency: string } } } // for input-currency.tsx
7
- currencies: { [key: string]: { name: string, symbol: string, digits: number } } // for input-currency.tsx
8
6
  env: string
9
7
  googleMapsApiKey?: string
10
8
  isDemo: boolean // implicitly defined by webpack
@@ -59,6 +57,24 @@ export type Store = {
59
57
  user?: User,
60
58
  }
61
59
 
60
+ // util.addressSchema
61
+ export type Address = {
62
+ city?: string
63
+ country?: string
64
+ full?: string
65
+ line1?: string
66
+ line2?: string
67
+ number?: string
68
+ postcode?: string
69
+ suburb?: string
70
+ unit?: string
71
+ area?: {
72
+ bottomLeft?: [number, number]
73
+ topRight?: [number, number]
74
+ }
75
+ location?: [number, number]
76
+ }
77
+
62
78
  export type Svg = React.FC<React.SVGProps<SVGElement>>
63
79
 
64
80
  /*
package/util.js CHANGED
@@ -35,7 +35,7 @@ let scrollbarCache
35
35
  let axiosNonce
36
36
 
37
37
  /**
38
- * Returns an address monastery schema which Google autocomplete should return
38
+ * Returns a monastery schema which matches the Google autocomplete output
39
39
  */
40
40
  export function addressSchema () {
41
41
  // Google autocomplete should return the following object