nitro-web 0.0.11 → 0.0.13

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.
Files changed (143) hide show
  1. package/.eslintrc.json +4 -19
  2. package/_example/.env +1 -1
  3. package/_example/client/config.ts +2 -1
  4. package/_example/client/index.ts +6 -24
  5. package/_example/components/index.tsx +1 -1
  6. package/_example/package.json +1 -1
  7. package/_example/server/config.js +6 -7
  8. package/_example/tailwind.config.js +1 -1
  9. package/_example/tsconfig.json +10 -2
  10. package/_example/types.ts +1 -0
  11. package/client/{app.js → app.tsx} +101 -99
  12. package/client/globals.ts +42 -0
  13. package/client/index.ts +52 -0
  14. package/client/store.ts +31 -0
  15. package/components/auth/auth.api.js +3 -2
  16. package/components/auth/{reset.jsx → reset.tsx} +21 -23
  17. package/components/auth/{signin.jsx → signin.tsx} +14 -16
  18. package/components/auth/{signup.jsx → signup.tsx} +15 -17
  19. package/components/billing/stripe.api.js +2 -1
  20. package/components/dashboard/{dashboard.jsx → dashboard.tsx} +3 -3
  21. package/components/partials/element/{accordion.jsx → accordion.tsx} +21 -13
  22. package/components/partials/element/avatar.tsx +40 -0
  23. package/components/partials/element/{button.jsx → button.tsx} +20 -16
  24. package/components/partials/element/{dropdown.jsx → dropdown.tsx} +32 -30
  25. package/components/partials/element/{github-link.jsx → github-link.tsx} +3 -3
  26. package/components/partials/element/{initials.jsx → initials.tsx} +11 -2
  27. package/components/partials/element/{message.jsx → message.tsx} +22 -23
  28. package/components/partials/element/{modal.jsx → modal.tsx} +4 -3
  29. package/components/partials/element/{sidebar.jsx → sidebar.tsx} +14 -7
  30. package/components/partials/element/{tooltip.jsx → tooltip.tsx} +11 -3
  31. package/components/partials/element/{topbar.jsx → topbar.tsx} +9 -7
  32. package/components/partials/form/{checkbox.jsx → checkbox.tsx} +13 -13
  33. package/components/partials/form/drop-handler.tsx +68 -0
  34. package/components/partials/form/{drop.jsx → drop.tsx} +51 -33
  35. package/components/partials/form/form-error.tsx +27 -0
  36. package/components/partials/form/{input-color.jsx → input-color.tsx} +27 -15
  37. package/components/partials/form/{input-currency.jsx → input-currency.tsx} +37 -32
  38. package/components/partials/form/{input-date.jsx → input-date.tsx} +4 -3
  39. package/components/partials/form/{input.jsx → input.tsx} +35 -19
  40. package/components/partials/form/{location.jsx → location.tsx} +21 -8
  41. package/components/partials/form/{select.jsx → select.tsx} +142 -143
  42. package/components/partials/form/{toggle.jsx → toggle.tsx} +10 -2
  43. package/components/partials/{is-first-render.js → is-first-render.ts} +1 -2
  44. package/components/partials/layout/layout1.tsx +29 -0
  45. package/components/partials/layout/{layout2.jsx → layout2.tsx} +3 -3
  46. package/components/partials/{styleguide.jsx → styleguide.tsx} +16 -19
  47. package/components/settings/{settings-account.jsx → settings-account.tsx} +9 -13
  48. package/components/settings/{settings-business.jsx → settings-business.tsx} +7 -8
  49. package/components/settings/{settings-team--member.jsx → settings-team--member.tsx} +4 -11
  50. package/components/settings/{settings-team.jsx → settings-team.tsx} +4 -8
  51. package/components/settings/settings.api.js +1 -0
  52. package/package.json +17 -31
  53. package/readme.md +1 -1
  54. package/server/email/index.js +2 -1
  55. package/server/index.js +1 -0
  56. package/server/models/company.js +2 -1
  57. package/server/models/user.js +2 -1
  58. package/server/router.js +8 -2
  59. package/tsconfig.json +36 -0
  60. package/types/required-globals.d.ts +39 -0
  61. package/types/util.d.ts +12 -2
  62. package/types/util.d.ts.map +1 -1
  63. package/types.ts +43 -0
  64. package/util.js +14 -34
  65. package/webpack.config.js +23 -4
  66. package/_example/types/index.d.ts +0 -13
  67. package/_example/types/twin.d.ts +0 -19
  68. package/client/index.js +0 -44
  69. package/components/partials/element/avatar.jsx +0 -31
  70. package/components/partials/form/drop-handler.jsx +0 -62
  71. package/components/partials/form/form-error.jsx +0 -21
  72. package/components/partials/layout/layout1.jsx +0 -38
  73. package/types/client/app.d.ts +0 -2
  74. package/types/client/app.d.ts.map +0 -1
  75. package/types/client/index.d.ts +0 -29
  76. package/types/client/index.d.ts.map +0 -1
  77. package/types/components/auth/reset.d.ts +0 -3
  78. package/types/components/auth/reset.d.ts.map +0 -1
  79. package/types/components/auth/signin.d.ts +0 -4
  80. package/types/components/auth/signin.d.ts.map +0 -1
  81. package/types/components/auth/signup.d.ts +0 -4
  82. package/types/components/auth/signup.d.ts.map +0 -1
  83. package/types/components/dashboard/dashboard.d.ts +0 -4
  84. package/types/components/dashboard/dashboard.d.ts.map +0 -1
  85. package/types/components/partials/element/accordion.d.ts +0 -7
  86. package/types/components/partials/element/accordion.d.ts.map +0 -1
  87. package/types/components/partials/element/avatar.d.ts +0 -8
  88. package/types/components/partials/element/avatar.d.ts.map +0 -1
  89. package/types/components/partials/element/button.d.ts +0 -11
  90. package/types/components/partials/element/button.d.ts.map +0 -1
  91. package/types/components/partials/element/dropdown.d.ts +0 -17
  92. package/types/components/partials/element/dropdown.d.ts.map +0 -1
  93. package/types/components/partials/element/initials.d.ts +0 -9
  94. package/types/components/partials/element/initials.d.ts.map +0 -1
  95. package/types/components/partials/element/message.d.ts +0 -2
  96. package/types/components/partials/element/message.d.ts.map +0 -1
  97. package/types/components/partials/element/modal.d.ts +0 -10
  98. package/types/components/partials/element/modal.d.ts.map +0 -1
  99. package/types/components/partials/element/sidebar.d.ts +0 -6
  100. package/types/components/partials/element/sidebar.d.ts.map +0 -1
  101. package/types/components/partials/element/tooltip.d.ts +0 -8
  102. package/types/components/partials/element/tooltip.d.ts.map +0 -1
  103. package/types/components/partials/element/topbar.d.ts +0 -8
  104. package/types/components/partials/element/topbar.d.ts.map +0 -1
  105. package/types/components/partials/form/checkbox.d.ts +0 -14
  106. package/types/components/partials/form/checkbox.d.ts.map +0 -1
  107. package/types/components/partials/form/drop-handler.d.ts +0 -6
  108. package/types/components/partials/form/drop-handler.d.ts.map +0 -1
  109. package/types/components/partials/form/drop.d.ts +0 -11
  110. package/types/components/partials/form/drop.d.ts.map +0 -1
  111. package/types/components/partials/form/form-error.d.ts +0 -6
  112. package/types/components/partials/form/form-error.d.ts.map +0 -1
  113. package/types/components/partials/form/input-color.d.ts +0 -10
  114. package/types/components/partials/form/input-color.d.ts.map +0 -1
  115. package/types/components/partials/form/input-currency.d.ts +0 -10
  116. package/types/components/partials/form/input-currency.d.ts.map +0 -1
  117. package/types/components/partials/form/input.d.ts +0 -9
  118. package/types/components/partials/form/input.d.ts.map +0 -1
  119. package/types/components/partials/form/location.d.ts +0 -12
  120. package/types/components/partials/form/location.d.ts.map +0 -1
  121. package/types/components/partials/form/select.d.ts +0 -27
  122. package/types/components/partials/form/select.d.ts.map +0 -1
  123. package/types/components/partials/form/toggle.d.ts +0 -9
  124. package/types/components/partials/form/toggle.d.ts.map +0 -1
  125. package/types/components/partials/is-first-render.d.ts +0 -2
  126. package/types/components/partials/is-first-render.d.ts.map +0 -1
  127. package/types/components/partials/layout/layout1.d.ts +0 -13
  128. package/types/components/partials/layout/layout1.d.ts.map +0 -1
  129. package/types/components/partials/layout/layout2.d.ts +0 -4
  130. package/types/components/partials/layout/layout2.d.ts.map +0 -1
  131. package/types/components/partials/not-found.d.ts +0 -2
  132. package/types/components/partials/not-found.d.ts.map +0 -1
  133. package/types/components/partials/styleguide.d.ts +0 -4
  134. package/types/components/partials/styleguide.d.ts.map +0 -1
  135. package/types/components/settings/settings-account.d.ts +0 -6
  136. package/types/components/settings/settings-account.d.ts.map +0 -1
  137. package/types/components/settings/settings-business.d.ts +0 -4
  138. package/types/components/settings/settings-business.d.ts.map +0 -1
  139. package/types/components/settings/settings-team--member.d.ts +0 -5
  140. package/types/components/settings/settings-team--member.d.ts.map +0 -1
  141. package/types/components/settings/settings-team.d.ts +0 -4
  142. package/types/components/settings/settings-team.d.ts.map +0 -1
  143. /package/components/partials/{not-found.jsx → not-found.tsx} +0 -0
@@ -1,166 +1,102 @@
1
- /*eslint-disable*/
2
1
  import { css } from 'twin.macro'
3
2
  import { twMerge } from 'tailwind-merge'
4
- import ReactSelect, { components, createFilter } from 'react-select'
3
+ import ReactSelect, { components, ControlProps, createFilter, OptionProps, SingleValueProps } from 'react-select'
4
+ import { ClearIndicatorProps, DropdownIndicatorProps, MultiValueRemoveProps } from 'react-select'
5
5
  import { ChevronDownIcon, CheckCircleIcon, XMarkIcon } from '@heroicons/react/20/solid'
6
- import * as util from '../../../util.js'
6
+ import { util } from 'nitro-web'
7
+ import { Errors } from 'types'
8
+
7
9
  const filterFn = createFilter()
8
10
 
9
- const selectStyles = {
10
- // Based off https://www.jussivirtanen.fi/writing/styling-react-select-with-tailwind
11
- // Input container
12
- control: {
13
- base: `rounded-md bg-white hover:cursor-pointer text-sm sm:text-sm/6 outline outline-1 -outline-offset-1 outline-input-border`,
14
- focus: `outline-2 -outline-offset-2 outline-primary`,
15
- error: `outline-danger`,
16
- },
17
- valueContainer: 'py-2 px-3 gap-1',
18
- // Input container objects
19
- input: {
20
- base: 'text-input',
21
- error: 'text-red-900',
22
- },
23
- multiValue: 'bg-primary text-white rounded items-center pl-2 pr-1.5 gap-1.5',
24
- multiValueLabel: '',
25
- multiValueRemove: `border border-primary-dark bg-white rounded-md text-dark hover:bg-red-50`,
26
- placeholder: 'text-input-placeholder',
27
- singleValue: {
28
- base: 'text-input',
29
- error: 'text-red-900',
30
- },
31
- // Icon indicators
32
- clearIndicator: 'text-gray-500 p-1 rounded-md hover:bg-red-50 hover:text-red-800',
33
- dropdownIndicator: 'p-1 hover:bg-gray-100 text-gray-500 rounded-md hover:text-black',
34
- indicatorsContainer: 'p-1 px-2 gap-1',
35
- indicatorSeparator: 'py-0.5 before:content-[""] before:block before:bg-gray-100 before:w-px before:h-full',
36
- // Dropdown menu
37
- menu: 'mt-1.5 border border-dropdown-ul-border bg-white rounded-md text-sm overflow-hidden shadow-dropdown-ul',
38
- groupHeading: 'ml-3 mt-2 mb-1 text-gray-500 text-sm',
39
- noOptionsMessage: 'm-1 text-gray-500 p-2 bg-gray-50 border border-dashed border-gray-200 rounded-sm',
40
- option: {
41
- base: 'relative px-3 py-2 !flex items-center gap-2 cursor-default',
42
- hover: 'bg-gray-50',
43
- selected: '!bg-gray-100 text-primary-dark',
44
- }
11
+ type GetSelectStyle = {
12
+ name: string
13
+ isFocused?: boolean
14
+ isSelected?: boolean
15
+ hasError?: boolean
16
+ usePrefixes?: boolean
45
17
  }
46
18
 
47
- export function getSelectStyle({ name, isFocused, isSelected, hasError, usePrefixes }) {
48
- // Returns a class list that conditionally includes hover/focus modifier classes, or uses CSS modifiers, e.g. hover:, focus:
49
- const obj = selectStyles[name]
50
- let output = obj?.base
51
- if (typeof obj == 'string') return obj // no modifiers
52
-
53
- if (usePrefixes) {
54
- if (obj.focus) output += ' ' + obj.focus.split(' ').map(part => `focus:${part}`).join(' ')
55
- if (obj.hover) output += ' ' + obj.hover.split(' ').map(part => `hover:${part}`).join(' ')
56
- } else {
57
- if (obj.focus && isFocused) output += ` ${obj.focus}`
58
- if (obj.hover && isFocused) output += ` ${obj.hover}`
59
- }
60
- if (obj.error && hasError) output += ` ${obj.error}`
61
- if (obj.selected && isSelected) output += ` ${obj.selected}`
62
-
63
- return twMerge(output)
19
+ /** Select (all other props are passed to react-select) **/
20
+ type SelectProps = {
21
+ /** field name or path on state (used to match errors), e.g. 'date', 'company.email' **/
22
+ name: string
23
+ /** name used if not provided **/
24
+ inputId?: string
25
+ /** The minimum width of the dropdown menu **/
26
+ minMenuWidth?: number
27
+ /** The prefix to add to the input **/
28
+ prefix?: string
29
+ /** The onChange handler **/
30
+ onChange?: (event: { target: { id: string, value: unknown } }) => void
31
+ /** The options to display in the dropdown **/
32
+ options: { value: unknown, label: string | React.ReactNode, fixed?: boolean, [key: string]: unknown }[]
33
+ /** The state object to get the value and check errors from **/
34
+ state?: { errors: Errors, [key: string]: unknown }
35
+ /** Select variations **/
36
+ type?: 'country'|'customer'|''
37
+ /** All other props are passed to react-select **/
38
+ [key: string]: unknown
64
39
  }
65
40
 
66
- /**
67
- * @param {string} name - field name or path on state (used to match errors), e.g. 'date', 'company.email'
68
- * @param {string} [minMenuWidth] - width of the dropdown menu
69
- * @param {string} [inputId] - name used if not provided
70
- * @param {function} [onChange] - e.g. (event) => onInputChange(event)
71
- * @param {object} [state] - object to get value from, and check errors against
72
- * @param {string} [type] - speical types: 'country', 'customer', 'customer-big'
73
- *
74
- * react-select prop quick reference (https://react-select.com/props#api):
75
- * isDisabled={false}
76
- * isMulti={false}
77
- * isSearchable={true}
78
- * options={[{ value: 'chocolate', label: 'Chocolate' }]}
79
- * placeholder="Select a color"
80
- * value={options.find(o => o.code == state.color)} // to clear you need to set to null, not undefined
81
- * isClearable={false}
82
- * menuIsOpen={false}
83
- */
84
- export function Select({ inputId, minMenuWidth, name, prefix='', onChange, options, state, type, ...props }) {
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
85
44
  if (!name) throw new Error('Select component requires a `name` and `options` prop')
86
45
 
87
46
  // Input is always controlled if state is passed in
88
47
  if (props.value) {
89
- var value = props.value
48
+ value = props.value
90
49
  } else if (typeof state == 'object') {
91
50
  value = options.find(o => o.value == util.deepFind(state, name))
92
51
  if (typeof value == 'undefined') value = ''
93
52
  }
94
53
 
95
54
  // An error matches this input path
96
- for (let item of (state?.errors || [])) {
97
- if (util.isRegex(name) && (item.title||'').match(name)) var hasError = item
55
+ for (const item of (state?.errors || [])) {
56
+ if (util.isRegex(name) && (item.title||'').match(name)) hasError = item
98
57
  else if (item.title == name) hasError = item
99
58
  }
100
59
 
101
-
102
- // classNames={{
103
- // // *Same classes as input.jsx*
104
- // // Based off https://www.jussivirtanen.fi/writing/styling-react-select-with-tailwind
105
- // //
106
- // // Input container
107
- // control: ({ isFocused }) => `rounded-md bg-white hover:cursor-pointer text-sm sm:text-sm/6
108
- // ${isFocused
109
- // ? `outline outline-2 -outline-offset-2 ${error ? 'outline-danger' : 'outline-primary'}`
110
- // : `outline outline-1 -outline-offset-1 ${error ? 'outline-danger' : 'outline-input-border'}`}`,
111
- // valueContainer: () => 'py-2 px-3 gap-1',
112
- // // Input container objects
113
- // input: () => `${error ? 'text-red-900' : 'text-input'}`,
114
- // multiValue: () => 'bg-primary text-white rounded items-center pl-2 pr-1.5 gap-1.5',
115
- // multiValueLabel: () => '',
116
- // multiValueRemove: () => `border border-primary-dark bg-white rounded-md text-dark hover:bg-red-50`,
117
- // placeholder: () => 'text-input-placeholder',
118
- // singleValue: () => `${error ? 'text-red-900' : 'text-input'}`,
119
- // // Indicators
120
- // clearIndicator: () =>'text-gray-500 p-1 rounded-md hover:bg-red-50 hover:text-red-800',
121
- // dropdownIndicator: () => 'p-1 hover:bg-gray-100 text-gray-500 rounded-md hover:text-black',
122
- // indicatorsContainer: () => 'p-1 px-2 gap-1',
123
- // indicatorSeparator: () => 'py-0.5 before:content-[""] before:block before:bg-gray-100 before:w-px before:h-full',
124
- // // Dropmenu
125
- // menu: () => 'mt-1.5 border border-dropdown-ul-border bg-white rounded-md text-sm overflow-hidden shadow-dropdown-ul',
126
- // groupHeading: () => 'ml-3 mt-2 mb-1 text-gray-500 text-sm',
127
- // noOptionsMessage: () => 'm-1 text-gray-500 p-2 bg-gray-50 border border-dashed border-gray-200 rounded-sm',
128
- // option: ({ isFocused, isSelected }) => `hover:cursor-pointer px-3 py-2 !flex items-center gap-2
129
- // ${isFocused ? 'bg-gray-50 active:bg-gray-200' : ''}
130
- // ${isSelected ? 'bg-gray-100 text-primary-dark' : ''}`,
131
- // }}
132
-
133
-
134
60
  return (
135
61
  <div css={style} class="mt-input-before mb-input-after">
136
62
  <ReactSelect
63
+ /**
64
+ * react-select prop quick reference (https://react-select.com/props#api):
65
+ * isDisabled={false}
66
+ * isMulti={false}
67
+ * isSearchable={true}
68
+ * options={[{ value: 'chocolate', label: 'Chocolate' }]}
69
+ * placeholder="Select a color"
70
+ * value={options.find(o => o.code == state.color)} // to clear you need to set to null, not undefined
71
+ * isClearable={false}
72
+ * menuIsOpen={false}
73
+ */
137
74
  {...props}
138
- _prefix={prefix}
139
- _type={type||''}
140
- _error={hasError}
141
- key={value}
75
+ // @ts-expect-error
76
+ _nitro={{ prefix, type }}
77
+ key={value as string}
142
78
  unstyled={true}
143
79
  inputId={inputId || name}
144
80
  filterOption={(option, searchText) => {
145
- if (option.data?.fixed) return true
81
+ if ((option.data as {fixed?: boolean}).fixed) return true
146
82
  return filterFn(option, searchText)
147
83
  }}
148
84
  menuPlacement="auto"
149
85
  minMenuHeight={250}
150
- onChange={!onChange ? undefined : (o) => onChange({ target: { id: inputId || name, value: o?.value || o }})}
86
+ onChange={!onChange ? undefined : (o) => onChange({ target: { id: inputId || name, value: (o as {value?: unknown})?.value || o }})}
151
87
  options={options}
152
88
  value={value}
153
89
  classNames={{
154
90
  // Input container
155
- control: (p) => getSelectStyle({ name: 'control', hasError, ...p }),
91
+ control: (p) => getSelectStyle({ name: 'control', hasError: !!hasError, ...p }),
156
92
  valueContainer: () => getSelectStyle({ name: 'valueContainer' }),
157
93
  // Input container objects
158
- input: () => getSelectStyle({ name: 'input', hasError }),
94
+ input: () => getSelectStyle({ name: 'input', hasError: !!hasError }),
159
95
  multiValue: () => getSelectStyle({ name: 'multiValue' }),
160
96
  multiValueLabel: () => '',
161
97
  multiValueRemove: () => getSelectStyle({ name: 'multiValueRemove' }),
162
98
  placeholder: () => getSelectStyle({ name: 'placeholder' }),
163
- singleValue: () => getSelectStyle({ name: 'singleValue', hasError }),
99
+ singleValue: () => getSelectStyle({ name: 'singleValue', hasError: !!hasError }),
164
100
  // Indicators
165
101
  clearIndicator: () => getSelectStyle({ name: 'clearIndicator' }),
166
102
  dropdownIndicator: () => getSelectStyle({ name: 'dropdownIndicator' }),
@@ -176,23 +112,25 @@ export function Select({ inputId, minMenuWidth, name, prefix='', onChange, optio
176
112
  Control,
177
113
  SingleValue,
178
114
  Option,
179
- DropdownIndicator, ClearIndicator, MultiValueRemove,
115
+ DropdownIndicator,
116
+ ClearIndicator,
117
+ MultiValueRemove,
180
118
  }}
181
119
  styles={{
182
120
  menu: (base) => ({
183
- ...base, minWidth: minMenuWidth
121
+ ...base, minWidth: minMenuWidth,
184
122
  }),
185
123
  // On mobile, the label will truncate automatically, so we want to
186
124
  // override that behaviour.
187
125
  multiValueLabel: (base) => ({
188
126
  ...base,
189
- whiteSpace: "normal",
190
- overflow: "visible",
127
+ whiteSpace: 'normal',
128
+ overflow: 'visible',
191
129
  }),
192
130
  control: (base) => ({
193
131
  ...base,
194
132
  outline: undefined,
195
- transition: "none",
133
+ transition: 'none',
196
134
  }),
197
135
  }}
198
136
  // menuIsOpen={true}
@@ -207,25 +145,27 @@ export function Select({ inputId, minMenuWidth, name, prefix='', onChange, optio
207
145
  )
208
146
  }
209
147
 
210
- function Control({ children, ...props }) {
148
+ function Control({ children, ...props }: ControlProps) {
211
149
  // Add flag and prefix to the input (control)
212
150
  // todo: check that the flag/prefix looks okay
213
- const selectedOption = props.getValue()[0] || {}
151
+ const selectedOption = props.getValue()[0]
152
+ const optionFlag = (selectedOption as { flag?: string })?.flag
153
+ const _nitro = (props.selectProps as { _nitro?: { prefix?: string, type?: string } })?._nitro
214
154
  return (
215
155
  <components.Control {...props}>
216
156
  {
217
157
  (() => {
218
- if (props.selectProps._prefix) {
158
+ if (_nitro?.prefix) {
219
159
  return (
220
160
  <>
221
- <span class="relative right-[2px]">{props.selectProps._prefix}</span>
161
+ <span class="relative right-[2px]">{_nitro?.prefix}</span>
222
162
  {children}
223
163
  </>
224
164
  )
225
- } else if (props.selectProps._type == 'country') {
165
+ } else if (_nitro?.type == 'country') {
226
166
  return (
227
167
  <>
228
- <Flag flag={selectedOption.flag} />
168
+ { optionFlag && <Flag flag={optionFlag} /> }
229
169
  {children}
230
170
  </>
231
171
  )
@@ -238,27 +178,29 @@ function Control({ children, ...props }) {
238
178
  )
239
179
  }
240
180
 
241
- function SingleValue(props) {
242
- const selectedOption = props.getValue()[0] || {}
181
+ function SingleValue(props: SingleValueProps) {
182
+ const selectedOption = props.getValue()[0] as { labelControl?: string }
243
183
  return (
244
184
  <components.SingleValue {...props}>
245
- <>{selectedOption.labelControl || props.children}</>
185
+ <>{selectedOption?.labelControl || props.children}</>
246
186
  </components.SingleValue>
247
187
  )
248
188
  }
249
189
 
250
- function Option(props) {
190
+ function Option(props: OptionProps) {
251
191
  // todo: check that the flag looks okay
192
+ const data = props.data as { className?: string, flag?: string }
193
+ const _nitro = (props.selectProps as { _nitro?: { type?: string } })?._nitro
252
194
  return (
253
- <components.Option className={props.data.className} {...props}>
254
- { props.selectProps._type == 'country' && <Flag flag={props.data.flag} /> }
195
+ <components.Option className={data.className} {...props}>
196
+ { _nitro?.type == 'country' && <Flag flag={data.flag} /> }
255
197
  <span class="flex-auto">{props.label}</span>
256
198
  {props.isSelected && <CheckCircleIcon className="size-[22px] text-primary -my-1 -mx-1" />}
257
199
  </components.Option>
258
200
  )
259
201
  }
260
202
 
261
- const DropdownIndicator = (props) => {
203
+ const DropdownIndicator = (props: DropdownIndicatorProps) => {
262
204
  return (
263
205
  <components.DropdownIndicator {...props}>
264
206
  <ChevronDownIcon className="size-6 -my-0.5 -mx-1" />
@@ -266,7 +208,7 @@ const DropdownIndicator = (props) => {
266
208
  )
267
209
  }
268
210
 
269
- const ClearIndicator = (props) => {
211
+ const ClearIndicator = (props: ClearIndicatorProps) => {
270
212
  return (
271
213
  <components.ClearIndicator {...props}>
272
214
  <XMarkIcon className="size-4 my-0.5" />
@@ -274,7 +216,7 @@ const ClearIndicator = (props) => {
274
216
  )
275
217
  }
276
218
 
277
- const MultiValueRemove = (props) => {
219
+ const MultiValueRemove = (props: MultiValueRemoveProps) => {
278
220
  return (
279
221
  <components.MultiValueRemove {...props}>
280
222
  <XMarkIcon className="size-4 p-[1px]" />
@@ -282,8 +224,7 @@ const MultiValueRemove = (props) => {
282
224
  )
283
225
  }
284
226
 
285
-
286
- function Flag({ flag }) {
227
+ function Flag({ flag }: { flag?: string }) {
287
228
  if (!flag) return null
288
229
  // todo: public needs to come from webpack
289
230
  const publicPath = '/'
@@ -292,7 +233,65 @@ function Flag({ flag }) {
292
233
  )
293
234
  }
294
235
 
295
- const style = () => css`
236
+ const selectStyles = {
237
+ // Based off https://www.jussivirtanen.fi/writing/styling-react-select-with-tailwind
238
+ // Input container
239
+ control: {
240
+ base: 'rounded-md bg-white hover:cursor-pointer text-sm sm:text-sm/6 outline outline-1 -outline-offset-1 outline-input-border',
241
+ focus: 'outline-2 -outline-offset-2 outline-primary',
242
+ error: 'outline-danger',
243
+ },
244
+ valueContainer: 'py-2 px-3 gap-1',
245
+ // Input container objects
246
+ input: {
247
+ base: 'text-input',
248
+ error: 'text-red-900',
249
+ },
250
+ multiValue: 'bg-primary text-white rounded items-center pl-2 pr-1.5 gap-1.5',
251
+ multiValueLabel: '',
252
+ multiValueRemove: 'border border-primary-dark bg-white rounded-md text-dark hover:bg-red-50',
253
+ placeholder: 'text-input-placeholder',
254
+ singleValue: {
255
+ base: 'text-input',
256
+ error: 'text-red-900',
257
+ },
258
+ // Icon indicators
259
+ clearIndicator: 'text-gray-500 p-1 rounded-md hover:bg-red-50 hover:text-red-800',
260
+ dropdownIndicator: 'p-1 hover:bg-gray-100 text-gray-500 rounded-md hover:text-black',
261
+ indicatorsContainer: 'p-1 px-2 gap-1',
262
+ indicatorSeparator: 'py-0.5 before:content-[""] before:block before:bg-gray-100 before:w-px before:h-full',
263
+ // Dropdown menu
264
+ menu: 'mt-1.5 border border-dropdown-ul-border bg-white rounded-md text-sm overflow-hidden shadow-dropdown-ul',
265
+ groupHeading: 'ml-3 mt-2 mb-1 text-gray-500 text-sm',
266
+ noOptionsMessage: 'm-1 text-gray-500 p-2 bg-gray-50 border border-dashed border-gray-200 rounded-sm',
267
+ option: {
268
+ base: 'relative px-3 py-2 !flex items-center gap-2 cursor-default',
269
+ hover: 'bg-gray-50',
270
+ selected: '!bg-gray-100 text-primary-dark',
271
+ },
272
+ }
273
+
274
+ export function getSelectStyle({ name, isFocused, isSelected, hasError, usePrefixes }: GetSelectStyle) {
275
+ // Returns a class list that conditionally includes hover/focus modifier classes, or uses CSS modifiers, e.g. hover:, focus:
276
+ // @ts-expect-error
277
+ const obj = selectStyles[name]
278
+ let output = obj?.base
279
+ if (typeof obj == 'string') return obj // no modifiers
280
+
281
+ if (usePrefixes) {
282
+ if (obj.focus) output += ' ' + obj.focus.split(' ').map((part: string) => `focus:${part}`).join(' ')
283
+ if (obj.hover) output += ' ' + obj.hover.split(' ').map((part: string) => `hover:${part}`).join(' ')
284
+ } else {
285
+ if (obj.focus && isFocused) output += ` ${obj.focus}`
286
+ if (obj.hover && isFocused) output += ` ${obj.hover}`
287
+ }
288
+ if (obj.error && hasError) output += ` ${obj.error}`
289
+ if (obj.selected && isSelected) output += ` ${obj.selected}`
290
+
291
+ return twMerge(output)
292
+ }
293
+
294
+ const style = css`
296
295
  /*
297
296
  todo: add these as tailwind classes
298
297
 
@@ -1,5 +1,13 @@
1
- export function Toggle({ name, id, subtext, text, type = 'checkbox', ...props }) {
2
- if (!name) throw new Error('Toggle requires a `name` prop')
1
+ type ToggleProps = {
2
+ className?: string
3
+ id?: string
4
+ name: string
5
+ subtext?: string
6
+ text?: string
7
+ type?: 'checkbox'
8
+ }
9
+
10
+ export function Toggle({ name, id, subtext, text, type='checkbox', ...props }: ToggleProps) {
3
11
  id = id || name
4
12
  // https://tailwindui.com/components/application-ui/forms/checkboxes#component-744ed4fa65ba36b925701eb4da5c6e31
5
13
  return (
@@ -1,8 +1,7 @@
1
- export function IsFirstRender(delay) {
1
+ export function IsFirstRender(delay?: number) {
2
2
  /*
3
3
  * Checks if the current render of a react component is the first
4
4
  * E.g. const isFirst = isFirstRender()
5
- * @param {boolean} delay
6
5
  * @link https://stackoverflow.com/a/56267719/1900648
7
6
  * @return boolean
8
7
  */
@@ -0,0 +1,29 @@
1
+ import { css } from 'twin.macro'
2
+ import { Outlet } from 'react-router-dom'
3
+ import { Message, Sidebar, SidebarProps } from 'nitro-web'
4
+
5
+ export function Layout1({ Logo, menu, links }: SidebarProps) {
6
+ // Dashboard, app screens (only the <Outlet/> receives `params` and `location`)
7
+ return (
8
+ <div css={style} class="bg-[#F3F3F3]">
9
+ <Message />
10
+ <div class="flex-1">
11
+ <div class="wrapper lg:flex min-h-[100%] w-[100%] bg-[#FDFDFD] shadow-[0_0_40px_0_rgb(237_237_237)]">
12
+ <Sidebar Logo={Logo} menu={menu} links={links} />
13
+ <div class="py-10 px-14 flex-1">
14
+ <Outlet />
15
+ </div>
16
+ </div>
17
+ </div>
18
+ </div>
19
+ )
20
+ }
21
+
22
+ const style = css`
23
+ .wrapper {
24
+ position: relative;
25
+ max-width: 1800px;
26
+ margin: 0 auto;
27
+ }
28
+ `
29
+
@@ -1,9 +1,9 @@
1
1
  import { css } from 'twin.macro'
2
2
  import { Outlet } from 'react-router-dom'
3
- import { Message } from '../element/message.jsx'
3
+ import { Message } from 'nitro-web'
4
4
 
5
5
  // Signin, reset password, etc
6
- export function Layout2({ Logo }) {
6
+ export function Layout2({ Logo }: { Logo: React.ComponentType<{ alt: string; width: string }> }) {
7
7
  return (
8
8
  <div css={style} class="bg-[#F3F3F3]">
9
9
  <Message />
@@ -33,7 +33,7 @@ export function Layout2({ Logo }) {
33
33
  )
34
34
  }
35
35
 
36
- const style = () => css`
36
+ const style = css`
37
37
  .wrapper {
38
38
  position: relative;
39
39
  max-width: 1800px;
@@ -1,14 +1,9 @@
1
- import { getCountryOptions, getCurrencyOptions, ucFirst } from '../../util.js'
1
+ import { Drop, Dropdown, Input, Select, Button, Checkbox, GithubLink } from 'nitro-web'
2
+ import { getCountryOptions, getCurrencyOptions, ucFirst } from 'nitro-web/util'
2
3
  import { CheckIcon } from '@heroicons/react/20/solid'
3
- import { Input } from './form/input.jsx'
4
- import { Drop } from './form/drop.jsx'
5
- import { Select } from './form/select.jsx'
6
- import { Dropdown } from './element/dropdown.jsx'
7
- import { Button } from './element/button.jsx'
8
- import { Checkbox } from './form/checkbox.jsx'
9
- import { GithubLink } from './element/github-link.jsx'
4
+ import { Config } from 'types'
10
5
 
11
- export function Styleguide({ config }) {
6
+ export function Styleguide({ config }: { config: Config }) {
12
7
  const [customerSearch, setCustomerSearch] = useState('')
13
8
  const [state, setState] = useState({
14
9
  address: '',
@@ -41,7 +36,7 @@ export function Styleguide({ config }) {
41
36
  { label: 'Delete' },
42
37
  ]
43
38
 
44
- function onInputChange (e) {
39
+ function onInputChange (e: { target: { id: string, value: unknown } }) {
45
40
  if ((e.target.id == 'customer' || e.target.id == 'customer2') && e.target.value == '') {
46
41
  setCustomerSearch('')
47
42
  e.target.value = null // clear the selected value
@@ -49,7 +44,7 @@ export function Styleguide({ config }) {
49
44
  setState(s => ({ ...s, [e.target.id]: e.target.value }))
50
45
  }
51
46
 
52
- function onCustomerSearch (search) {
47
+ function onCustomerSearch (search: string) {
53
48
  setCustomerSearch(search || '')
54
49
  }
55
50
 
@@ -114,10 +109,10 @@ export function Styleguide({ config }) {
114
109
  <div><Button color="primary">primary (default)</Button></div>
115
110
  <div><Button color="secondary">secondary button</Button></div>
116
111
  <div><Button color="white">white button</Button></div>
117
- <div><Button color="primary-xs">*-xs button</Button></div>
118
- <div><Button color="primary-sm">*-sm button</Button></div>
119
- <div><Button color="primary-md">*-md (default)</Button></div>
120
- <div><Button color="primary-lg">*-lg button</Button></div>
112
+ <div><Button color="primary" size="xs">*-xs button</Button></div>
113
+ <div><Button color="primary" size="sm">*-sm button</Button></div>
114
+ <div><Button color="primary">*-md (default)</Button></div>
115
+ <div><Button color="primary" size="lg">*-lg button</Button></div>
121
116
  <div><Button IconLeft={<CheckIcon class="size-5 -my-5 -mx-0.5" />}>IconLeft=Element</Button></div>
122
117
  <div><Button IconRight="v">IconRight=&quot;v&quot;</Button></div>
123
118
  <div><Button IconRight2="v" className="w-[200px]">IconRight2=&quot;v&quot;</Button></div>
@@ -176,10 +171,12 @@ export function Styleguide({ config }) {
176
171
  className: 'bb',
177
172
  fixed: true,
178
173
  value: '',
179
- label: <>
180
- <b>New Customer</b>
181
- {customerSearch ? <> / Add <b>{ucFirst(customerSearch)}</b></> : ''}
182
- </>,
174
+ label: (
175
+ <>
176
+ <b>New Customer</b>
177
+ {customerSearch ? <> / Add <b>{ucFirst(customerSearch)}</b></> : ''}
178
+ </>
179
+ ),
183
180
  },
184
181
  { value: '1', label: 'Iron Man Industries' },
185
182
  { value: '2', label: 'Captain America' },
@@ -1,18 +1,14 @@
1
+ // @ts-nocheck
1
2
  // todo: finish tailwind conversion
2
- import * as util from '../../util.js'
3
- import SvgTick from '../../client/imgs/icons/tick.svg'
4
- import { Button } from '../partials/element/button.jsx'
5
- import { FormError } from '../partials/form/form-error.jsx'
6
- import { Input } from '../partials/form/input.jsx'
7
- import { Modal } from '../partials/element/modal.jsx'
8
- import { Topbar } from '../partials/element/topbar.jsx'
9
- import { Tabbar } from '../partials/element/tabbar.jsx'
3
+ import * as util from 'nitro-web/util'
4
+ import SvgTick from 'nitro-web/client/imgs/icons/tick.svg'
5
+ import { Button, FormError, Input, Modal, Topbar, Tabbar } from 'nitro-web'
10
6
 
11
7
  export function SettingsAccount() {
12
- let isLoading = useState('')
13
- let [removeModal, setRemoveModal] = useState()
14
- let [{user}, setStore] = sharedStore.useTracked()
15
- let [state, setState] = useState({
8
+ const isLoading = useState('')
9
+ const [removeModal, setRemoveModal] = useState()
10
+ const [{user}, setStore] = sharedStore.useTracked()
11
+ const [state, setState] = useState({
16
12
  avatar: user.avatar || '',
17
13
  email: user.email || '',
18
14
  firstName: user.firstName || '',
@@ -131,7 +127,7 @@ export function RemoveModal ({ show, setShow }) {
131
127
  }
132
128
 
133
129
  import { css } from 'twin.macro'
134
- const style = (_theme) => css`
130
+ const style = css`
135
131
  /* input[type='file'] {
136
132
  padding: 8px 18px;
137
133
  font-size: 12px;
@@ -1,11 +1,10 @@
1
- // todo: finish tailwind conversion
2
- import * as util from '../../util.js'
3
- import SvgTick from '../../client/imgs/icons/tick.svg'
4
- import { Button } from '../partials/element/button.jsx'
5
- import { Input } from '../partials/form/input.jsx'
6
- import { Select } from '../partials/form/select.jsx'
7
- import { Topbar } from '../partials/element/topbar.jsx'
8
- import { Tabbar } from '../partials/element/tabbar.jsx'
1
+ // @ts-nocheck
2
+ // todo: finish tailwind conversio
3
+
4
+ ////// look at the select type error below
5
+ import * as util from 'nitro-web/util'
6
+ import SvgTick from 'nitro-web/client/imgs/icons/tick.svg'
7
+ import { Button, Input, Select, Topbar, Tabbar } from 'nitro-web'
9
8
 
10
9
  export function SettingsBusiness({ config }) {
11
10
  const isLoading = useState('')
@@ -1,11 +1,7 @@
1
+ // @ts-nocheck
1
2
  // todo: finish tailwind conversion
2
- import { css } from 'twin.macro'
3
- import { Button } from '../partials/element/button.jsx'
4
- import { Modal } from '../partials/element/modal.jsx'
5
- import { FormError } from '../partials/form/form-error.jsx'
6
- import { Input } from '../partials/form/input.jsx'
7
- import { Select } from '../partials/form/select.jsx'
8
- import SvgTick from '../../client/imgs/icons/tick.svg'
3
+ import { Button, FormError, Input, Modal, Select } from 'nitro-web'
4
+ import SvgTick from 'nitro-web/client/imgs/icons/tick.svg'
9
5
 
10
6
  export function SettingsTeamMember ({ showModal, setShowModal }) {
11
7
  // @param {object} showModal - user
@@ -29,7 +25,7 @@ export function SettingsTeamMember ({ showModal, setShowModal }) {
29
25
  }
30
26
 
31
27
  return (
32
- <Modal show={showModal} setShow={setShowModal} css={style} class="p-modal">
28
+ <Modal show={showModal} setShow={setShowModal} class="p-modal">
33
29
 
34
30
  <h2 class="h2"><em>Add</em> Team Member</h2>
35
31
  <p class="subtitle">Invite a new team member to collaborate with you on Nitro.</p>
@@ -103,6 +99,3 @@ export function SettingsTeamMember ({ showModal, setShowModal }) {
103
99
  </Modal>
104
100
  )
105
101
  }
106
-
107
- const style = (_theme) => css`
108
- `