nitro-web 0.0.10 → 0.0.12

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 (144) 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 +0 -1
  7. package/_example/server/config.js +6 -7
  8. package/_example/tailwind.config.js +1 -1
  9. package/_example/tsconfig.json +5 -1
  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.tsx +16 -0
  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 +14 -28
  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 +3 -2
  59. package/tsconfig.json +31 -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/element/github-link.jsx +0 -14
  71. package/components/partials/form/drop-handler.jsx +0 -62
  72. package/components/partials/form/form-error.jsx +0 -21
  73. package/components/partials/layout/layout1.jsx +0 -38
  74. package/types/client/app.d.ts +0 -2
  75. package/types/client/app.d.ts.map +0 -1
  76. package/types/client/index.d.ts +0 -29
  77. package/types/client/index.d.ts.map +0 -1
  78. package/types/components/auth/reset.d.ts +0 -3
  79. package/types/components/auth/reset.d.ts.map +0 -1
  80. package/types/components/auth/signin.d.ts +0 -4
  81. package/types/components/auth/signin.d.ts.map +0 -1
  82. package/types/components/auth/signup.d.ts +0 -4
  83. package/types/components/auth/signup.d.ts.map +0 -1
  84. package/types/components/dashboard/dashboard.d.ts +0 -4
  85. package/types/components/dashboard/dashboard.d.ts.map +0 -1
  86. package/types/components/partials/element/accordion.d.ts +0 -7
  87. package/types/components/partials/element/accordion.d.ts.map +0 -1
  88. package/types/components/partials/element/avatar.d.ts +0 -8
  89. package/types/components/partials/element/avatar.d.ts.map +0 -1
  90. package/types/components/partials/element/button.d.ts +0 -11
  91. package/types/components/partials/element/button.d.ts.map +0 -1
  92. package/types/components/partials/element/dropdown.d.ts +0 -17
  93. package/types/components/partials/element/dropdown.d.ts.map +0 -1
  94. package/types/components/partials/element/initials.d.ts +0 -9
  95. package/types/components/partials/element/initials.d.ts.map +0 -1
  96. package/types/components/partials/element/message.d.ts +0 -2
  97. package/types/components/partials/element/message.d.ts.map +0 -1
  98. package/types/components/partials/element/modal.d.ts +0 -10
  99. package/types/components/partials/element/modal.d.ts.map +0 -1
  100. package/types/components/partials/element/sidebar.d.ts +0 -6
  101. package/types/components/partials/element/sidebar.d.ts.map +0 -1
  102. package/types/components/partials/element/tooltip.d.ts +0 -8
  103. package/types/components/partials/element/tooltip.d.ts.map +0 -1
  104. package/types/components/partials/element/topbar.d.ts +0 -8
  105. package/types/components/partials/element/topbar.d.ts.map +0 -1
  106. package/types/components/partials/form/checkbox.d.ts +0 -14
  107. package/types/components/partials/form/checkbox.d.ts.map +0 -1
  108. package/types/components/partials/form/drop-handler.d.ts +0 -6
  109. package/types/components/partials/form/drop-handler.d.ts.map +0 -1
  110. package/types/components/partials/form/drop.d.ts +0 -11
  111. package/types/components/partials/form/drop.d.ts.map +0 -1
  112. package/types/components/partials/form/form-error.d.ts +0 -6
  113. package/types/components/partials/form/form-error.d.ts.map +0 -1
  114. package/types/components/partials/form/input-color.d.ts +0 -10
  115. package/types/components/partials/form/input-color.d.ts.map +0 -1
  116. package/types/components/partials/form/input-currency.d.ts +0 -10
  117. package/types/components/partials/form/input-currency.d.ts.map +0 -1
  118. package/types/components/partials/form/input.d.ts +0 -9
  119. package/types/components/partials/form/input.d.ts.map +0 -1
  120. package/types/components/partials/form/location.d.ts +0 -12
  121. package/types/components/partials/form/location.d.ts.map +0 -1
  122. package/types/components/partials/form/select.d.ts +0 -27
  123. package/types/components/partials/form/select.d.ts.map +0 -1
  124. package/types/components/partials/form/toggle.d.ts +0 -9
  125. package/types/components/partials/form/toggle.d.ts.map +0 -1
  126. package/types/components/partials/is-first-render.d.ts +0 -2
  127. package/types/components/partials/is-first-render.d.ts.map +0 -1
  128. package/types/components/partials/layout/layout1.d.ts +0 -13
  129. package/types/components/partials/layout/layout1.d.ts.map +0 -1
  130. package/types/components/partials/layout/layout2.d.ts +0 -4
  131. package/types/components/partials/layout/layout2.d.ts.map +0 -1
  132. package/types/components/partials/not-found.d.ts +0 -2
  133. package/types/components/partials/not-found.d.ts.map +0 -1
  134. package/types/components/partials/styleguide.d.ts +0 -4
  135. package/types/components/partials/styleguide.d.ts.map +0 -1
  136. package/types/components/settings/settings-account.d.ts +0 -6
  137. package/types/components/settings/settings-account.d.ts.map +0 -1
  138. package/types/components/settings/settings-business.d.ts +0 -4
  139. package/types/components/settings/settings-business.d.ts.map +0 -1
  140. package/types/components/settings/settings-team--member.d.ts +0 -5
  141. package/types/components/settings/settings-team--member.d.ts.map +0 -1
  142. package/types/components/settings/settings-team.d.ts +0 -4
  143. package/types/components/settings/settings-team.d.ts.map +0 -1
  144. /package/components/partials/{not-found.jsx → not-found.tsx} +0 -0
@@ -0,0 +1,31 @@
1
+ import { createContainer } from 'react-tracked'
2
+ import { Store } from 'types'
3
+
4
+
5
+ export type BeforeUpdate = (prevStore: Store | null, newData: Store) => Store
6
+
7
+ let initData: Store
8
+ let beforeUpdate: BeforeUpdate = (prevStore, newData) => newData
9
+
10
+ const container = createContainer(() => {
11
+ const [store, setStore] = useState(() => beforeUpdate(null, initData || exposedData || {}))
12
+
13
+ // Wrap the setState function to always run beforeUpdate
14
+ const wrappedSetStore = (updater: (prevStore: Store) => Store) => {
15
+ if (typeof updater === 'function') {
16
+ setStore((prevStore: Store) => beforeUpdate(prevStore, updater(prevStore)))
17
+ } else {
18
+ setStore((prevStore: Store) => beforeUpdate(prevStore, updater))
19
+ }
20
+ }
21
+
22
+ exposedData = store
23
+ return [store, wrappedSetStore]
24
+ })
25
+
26
+ export let exposedData: Store
27
+ export const { Provider, useTracked } = container
28
+ export function beforeCreate(_initData: Store, _beforeUpdate: BeforeUpdate) {
29
+ initData = _initData // normally provided from a /login or /state request data
30
+ beforeUpdate = _beforeUpdate
31
+ }
@@ -1,11 +1,12 @@
1
+ // @ts-nocheck
1
2
  import MongoStore from 'connect-mongo'
2
3
  import crypto from 'crypto'
3
4
  import expressSession from 'express-session'
4
5
  import passport from 'passport'
5
6
  import passportLocal from 'passport-local'
6
7
  import db from 'monastery'
7
- import { sendEmail } from '../../server/email/index.js'
8
- import * as util from '../../util.js'
8
+ import { sendEmail } from 'nitro-web/server'
9
+ import * as util from 'nitro-web/util'
9
10
  // import stripeController from '../billing/stripe.api.js'
10
11
 
11
12
  let config = {}
@@ -1,22 +1,19 @@
1
- import * as util from '../../util.js'
2
- import { Topbar } from '../partials/element/topbar.jsx'
3
- import { Input } from '../partials/form/input.jsx'
4
- import { FormError } from '../partials/form/form-error.jsx'
5
- import { Button } from '../partials/element/button.jsx'
1
+ import { Topbar, Input, FormError, Button, util } from 'nitro-web'
2
+ import { Errors } from 'types'
6
3
 
7
4
  export function ResetInstructions() {
8
5
  const navigate = useNavigate()
9
6
  const isLoading = useState('')
10
- const [, setStore] = sharedStore.useTracked()
11
- const [state, setState] = useState({ email: '' })
7
+ const [, setStore] = useTracked()
8
+ const [state, setState] = useState({ email: '', errors: [] as Errors })
12
9
 
13
- async function onSubmit (e) {
10
+ async function onSubmit (event: React.FormEvent<HTMLFormElement>) {
14
11
  try {
15
- await util.request(e, 'post /api/reset-instructions', state, isLoading)
12
+ await util.request(event, 'post /api/reset-instructions', state, isLoading)
16
13
  setStore(s => ({ ...s, message: 'Done! Please check your email.' }))
17
14
  navigate('/signin')
18
- } catch (errors) {
19
- return setState({ ...state, errors })
15
+ } catch (e) {
16
+ return setState({ ...state, errors: e as Errors })
20
17
  }
21
18
  }
22
19
 
@@ -27,15 +24,15 @@ export function ResetInstructions() {
27
24
  <form onSubmit={onSubmit}>
28
25
  <div>
29
26
  <label for="email">Email Address</label>
30
- <Input name="email" type="email" state={state} onChange={onChange(setState)} placeholder="Your email address..." />
27
+ <Input name="email" type="email" state={state} onChange={onChange.bind(setState)} placeholder="Your email address..." />
31
28
  </div>
32
29
 
33
30
  <div class="mb-14">
34
31
  Remembered your password? You can <Link to="/signin" class="underline2 is-active">sign in here</Link>.
35
- <FormError state={state} class="pt-2" />
32
+ <FormError state={state} className="pt-2" />
36
33
  </div>
37
34
 
38
- <Button class="w-full" isLoading={isLoading[0]} type="submit">Email me a reset password link</Button>
35
+ <Button className="w-full" isLoading={!!isLoading[0]} type="submit">Email me a reset password link</Button>
39
36
  </form>
40
37
  </div>
41
38
  )
@@ -45,20 +42,21 @@ export function ResetPassword() {
45
42
  const navigate = useNavigate()
46
43
  const params = useParams()
47
44
  const isLoading = useState('')
48
- const [, setStore] = sharedStore.useTracked()
45
+ const [, setStore] = useTracked()
49
46
  const [state, setState] = useState(() => ({
50
47
  password: '',
51
48
  password2: '',
52
49
  token: params.token,
50
+ errors: [] as Errors,
53
51
  }))
54
52
 
55
- async function onSubmit (e) {
53
+ async function onSubmit (event: React.FormEvent<HTMLFormElement>) {
56
54
  try {
57
- const data = await util.request(e, 'post /api/reset-password', state, isLoading)
55
+ const data = await util.request(event, 'post /api/reset-password', state, isLoading)
58
56
  setStore(() => data)
59
57
  navigate('/')
60
- } catch (errors) {
61
- return setState({ ...state, errors })
58
+ } catch (e) {
59
+ return setState({ ...state, errors: e as Errors })
62
60
  }
63
61
  }
64
62
 
@@ -69,19 +67,19 @@ export function ResetPassword() {
69
67
  <form onSubmit={onSubmit}>
70
68
  <div>
71
69
  <label for="password">Your New Password</label>
72
- <Input name="password" type="password" state={state} onChange={onChange(setState)} />
70
+ <Input name="password" type="password" state={state} onChange={onChange.bind(setState)} />
73
71
  </div>
74
72
  <div>
75
73
  <label for="password2">Repeat Your New Password</label>
76
- <Input name="password2" type="password" state={state} onChange={onChange(setState)} />
74
+ <Input name="password2" type="password" state={state} onChange={onChange.bind(setState)} />
77
75
  </div>
78
76
 
79
77
  <div class="mb-14">
80
78
  Remembered your password? You can <Link to="/signin" class="underline2 is-active">sign in here</Link>.
81
- <FormError state={state} class="pt-2" />
79
+ <FormError state={state} className="pt-2" />
82
80
  </div>
83
81
 
84
- <Button class="w-full" isLoading={isLoading[0]} type="submit">Reset Password</Button>
82
+ <Button class="w-full" isLoading={!!isLoading[0]} type="submit">Reset Password</Button>
85
83
  </form>
86
84
  </div>
87
85
  )
@@ -1,18 +1,16 @@
1
- import * as util from '../../util.js'
2
- import { Topbar } from '../partials/element/topbar.jsx'
3
- import { Input } from '../partials/form/input.jsx'
4
- import { Button } from '../partials/element/button.jsx'
5
- import { FormError } from '../partials/form/form-error.jsx'
1
+ import { Topbar, Input, Button, FormError, util } from 'nitro-web'
2
+ import { Config, Errors } from 'types'
6
3
 
7
- export function Signin({ config }) {
4
+ export function Signin({ config }: { config: Config }) {
8
5
  const navigate = useNavigate()
9
6
  const location = useLocation()
10
7
  const isSignout = location.pathname == '/signout'
11
8
  const isLoading = useState(isSignout ? 'is-loading' : '')
12
- const [, setStore] = sharedStore.useTracked()
9
+ const [, setStore] = useTracked()
13
10
  const [state, setState] = useState({
14
- email: config.env == 'development' ? config.testEmail : '',
11
+ email: config.env == 'development' ? config.placeholderEmail : '',
15
12
  password: config.env == 'development' ? '1234' : '',
13
+ errors: [] as Errors,
16
14
  })
17
15
 
18
16
  useEffect(() => {
@@ -27,11 +25,11 @@ export function Signin({ config }) {
27
25
  util.axios().get('/api/signout')
28
26
  .then(() => isLoading[1](''))
29
27
  .then(() => navigate({ pathname: '/signin', search: location.search }, { replace: true }))
30
- .catch(err => console.error(err) || isLoading[1](''))
28
+ .catch(err => (console.error(err), isLoading[1]('')))
31
29
  }
32
30
  }, [isSignout])
33
31
 
34
- async function onSubmit (e) {
32
+ async function onSubmit (e: React.FormEvent<HTMLFormElement>) {
35
33
  try {
36
34
  const data = await util.request(e, 'post /api/signin', state, isLoading)
37
35
  isLoading[1]('is-loading')
@@ -40,8 +38,8 @@ export function Signin({ config }) {
40
38
  if (location.search.includes('redirect')) navigate(location.search.replace('?redirect=', ''))
41
39
  else navigate('/')
42
40
  }, 100)
43
- } catch (errors) {
44
- return setState({ ...state, errors })
41
+ } catch (e) {
42
+ return setState({ ...state, errors: e as Errors})
45
43
  }
46
44
  }
47
45
 
@@ -52,22 +50,22 @@ export function Signin({ config }) {
52
50
  <form onSubmit={onSubmit}>
53
51
  <div>
54
52
  <label for="email">Email Address</label>
55
- <Input name="email" type="email" state={state} onChange={onChange(setState)} placeholder="Your email address..." />
53
+ <Input name="email" type="email" state={state} onChange={onChange.bind(setState)} placeholder="Your email address..." />
56
54
  </div>
57
55
  <div>
58
56
  <div class="flex justify-between">
59
57
  <label for="password">Password</label>
60
58
  <Link to="/reset" class="label underline2">Forgot?</Link>
61
59
  </div>
62
- <Input name="password" type="password" state={state} onChange={onChange(setState)}/>
60
+ <Input name="password" type="password" state={state} onChange={onChange.bind(setState)}/>
63
61
  </div>
64
62
 
65
63
  <div class="mb-14">
66
64
  Don&apos;t have an account? You can <Link to="/signup" class="underline2 is-active">sign up here</Link>.
67
- <FormError state={state} class="pt-2" />
65
+ <FormError state={state} className="pt-2" />
68
66
  </div>
69
67
 
70
- <Button class="w-full" isLoading={isLoading[0]} type="submit">Sign In</Button>
68
+ <Button class="w-full" isLoading={!!isLoading[0]} type="submit">Sign In</Button>
71
69
  </form>
72
70
  </div>
73
71
  )
@@ -1,28 +1,26 @@
1
- import * as util from '../../util.js'
2
- import { Topbar } from '../partials/element/topbar.jsx'
3
- import { Input } from '../partials/form/input.jsx'
4
- import { Button } from '../partials/element/button.jsx'
5
- import { FormError } from '../partials/form/form-error.jsx'
1
+ import { Button, Input, FormError, Topbar, util } from 'nitro-web'
2
+ import { Config, Errors } from 'types'
6
3
 
7
- export function Signup({ config }) {
4
+ export function Signup({ config }: { config: Config}) {
8
5
  const navigate = useNavigate()
9
6
  const isLoading = useState('')
10
- const [, setStore] = sharedStore.useTracked()
7
+ const [, setStore] = useTracked()
11
8
  const [state, setState] = useState({
12
- email: config.env === 'development' ? config.testEmail : '',
9
+ email: config.env === 'development' ? config.placeholderEmail : '',
13
10
  name: config.env === 'development' ? 'Bruce Wayne' : '',
14
11
  business: { name: config.env === 'development' ? 'Wayne Enterprises' : '' },
15
12
  password: config.env === 'development' ? '1234' : '',
13
+ errors: [] as Errors,
16
14
  })
17
15
 
18
- async function onSubmit (e) {
16
+ async function onSubmit (e: React.FormEvent<HTMLFormElement>) {
19
17
  try {
20
18
  const data = await util.request(e, 'post /api/signup', state, isLoading)
21
19
  isLoading[1]('is-loading')
22
20
  setStore(() => data)
23
21
  setTimeout(() => navigate('/'), 0) // wait for setStore
24
- } catch (errors) {
25
- return setState({ ...state, errors })
22
+ } catch (e) {
23
+ return setState({ ...state, errors: e as Errors })
26
24
  }
27
25
  }
28
26
 
@@ -34,28 +32,28 @@ export function Signup({ config }) {
34
32
  <div class="grid grid-cols-2 gap-6">
35
33
  <div>
36
34
  <label for="name">Your Name</label>
37
- <Input name="name" placeholder="E.g. Tony Stark" state={state} onChange={onChange(setState)} />
35
+ <Input name="name" placeholder="E.g. Tony Stark" state={state} onChange={onChange.bind(setState)} />
38
36
  </div>
39
37
  <div>
40
38
  <label for="business.name">Company Name</label>
41
- <Input name="business.name" placeholder="E.g. Stark Industries" state={state} onChange={onChange(setState)} />
39
+ <Input name="business.name" placeholder="E.g. Stark Industries" state={state} onChange={onChange.bind(setState)} />
42
40
  </div>
43
41
  </div>
44
42
  <div>
45
43
  <label for="email">Email Address</label>
46
- <Input name="email" type="email" state={state} onChange={onChange(setState)} placeholder="Your email address..." />
44
+ <Input name="email" type="email" state={state} onChange={onChange.bind(setState)} placeholder="Your email address..." />
47
45
  </div>
48
46
  <div>
49
47
  <label for="password">Password</label>
50
- <Input name="password" type="password" state={state} onChange={onChange(setState)}/>
48
+ <Input name="password" type="password" state={state} onChange={onChange.bind(setState)}/>
51
49
  </div>
52
50
 
53
51
  <div class="mb-14">
54
52
  Already have an account? You can <Link to="/signin" class="underline2 is-active">sign in here</Link>.
55
- <FormError state={state} class="pt-2" />
53
+ <FormError state={state} className="pt-2" />
56
54
  </div>
57
55
 
58
- <Button class="w-full" isLoading={isLoading[0]} type="submit">Create Account</Button>
56
+ <Button class="w-full" isLoading={!!isLoading[0]} type="submit">Create Account</Button>
59
57
  </form>
60
58
  </div>
61
59
  )
@@ -1,6 +1,7 @@
1
+ // @ts-nocheck
1
2
  import Stripe from 'stripe'
2
3
  import db from 'monastery'
3
- import * as util from '../../util.js'
4
+ import * as util from 'nitro-web/util'
4
5
 
5
6
  let stripe = undefined
6
7
  let stripeProducts = []
@@ -1,7 +1,7 @@
1
1
  import { css, theme } from 'twin.macro'
2
2
 
3
- export function Dashboard({ config }) {
4
- const [store] = sharedStore.useTracked()
3
+ export function Dashboard({ config }: { config: { isStatic?: boolean } }) {
4
+ const [store] = useTracked()
5
5
  const textColor = store.apiAvailable ? 'text-green-700' : 'text-pink-700'
6
6
  const fillColor = store.apiAvailable ? 'fill-green-500' : 'fill-pink-500'
7
7
  const bgColor = store.apiAvailable ? 'bg-green-100' : 'bg-pink-100'
@@ -17,7 +17,7 @@ export function Dashboard({ config }) {
17
17
  <svg viewBox="0 0 6 6" aria-hidden="true" className={`size-1.5 ${fillColor}`}>
18
18
  <circle r={3} cx={3} cy={3} />
19
19
  </svg>
20
- { store.apiAvailable ? 'API Available' : `API Unavailable${config.isDemo ? ' (Static Example)' : ''}` }
20
+ { store.apiAvailable ? 'API Available' : `API Unavailable${config.isStatic ? ' (Static Example)' : ''}` }
21
21
  </span>
22
22
  </p>
23
23
  </div>
@@ -1,7 +1,15 @@
1
+
1
2
  import { css } from 'twin.macro'
2
- import { IsFirstRender } from '../is-first-render.js'
3
+ import { IsFirstRender } from 'nitro-web'
4
+
5
+ type AccordionProps = {
6
+ children: React.ReactNode
7
+ className?: string
8
+ expanded?: boolean
9
+ onChange?: (event: React.MouseEvent<HTMLDivElement>, index: number) => void
10
+ }
3
11
 
4
- export function Accordion({ children, className, expanded, onChange }) {
12
+ export function Accordion({ children, className, expanded, onChange }: AccordionProps) {
5
13
  /**
6
14
  * @param {rxjs} children - first child is the header, second child is the contents
7
15
  * <Accordion>
@@ -10,12 +18,12 @@ export function Accordion({ children, className, expanded, onChange }) {
10
18
  * @param {boolean} <expanded> - initial value (or controlled value if onChange is passed)
11
19
  * @param {function} <onChange> - called when the header is clicked
12
20
  */
13
- let [preState, setPreState] = useState(expanded)
14
- let [state, setState] = useState(expanded)
15
- let [height, setHeight] = useState('auto')
16
- let isFirst = IsFirstRender()
17
- let el = useRef()
18
- let style = () => css`
21
+ const [preState, setPreState] = useState(expanded)
22
+ const [state, setState] = useState(expanded)
23
+ const [height, setHeight] = useState('auto')
24
+ const isFirst = IsFirstRender()
25
+ const el = useRef<HTMLDivElement>(null)
26
+ const style = css`
19
27
  &>:last-child {
20
28
  height: 0;
21
29
  overflow: hidden;
@@ -34,7 +42,7 @@ export function Accordion({ children, className, expanded, onChange }) {
34
42
  useEffect(() => {
35
43
  // Calulcate height first before opening and closing
36
44
  if (!isFirst) {
37
- setHeight((_o) => el.current.children[1].scrollHeight + 'px' + (preState ? '-' : ''))
45
+ setHeight((_o) => el.current?.children[1].scrollHeight + 'px' + (preState ? '-' : ''))
38
46
  }
39
47
  }, [preState])
40
48
 
@@ -52,9 +60,9 @@ export function Accordion({ children, className, expanded, onChange }) {
52
60
  return () => timeout && clearTimeout(timeout)
53
61
  }, [height])
54
62
 
55
- let onClick = function(e) {
63
+ const onClick = function(e: React.MouseEvent<HTMLDivElement>) {
56
64
  // Click came from inside the accordion header/summary
57
- if (e.currentTarget.children[0].contains(e.target) || e.currentTarget.children[0] == e.target) {
65
+ if (e.currentTarget.children[0].contains(e.target as Node) || e.currentTarget.children[0] == e.target) {
58
66
  if (onChange) {
59
67
  onChange(e, getElementIndex(e.currentTarget))
60
68
  } else {
@@ -63,9 +71,9 @@ export function Accordion({ children, className, expanded, onChange }) {
63
71
  }
64
72
  }
65
73
 
66
- let getElementIndex = function(node) {
74
+ const getElementIndex = function(node: HTMLElement) {
67
75
  let index = 0
68
- while ((node = node.previousElementSibling)) index++
76
+ while ((node = node.previousElementSibling as HTMLElement)) index++
69
77
  return index
70
78
  }
71
79
 
@@ -0,0 +1,40 @@
1
+ import { Initials } from 'nitro-web'
2
+ import { s3Image } from 'nitro-web/util'
3
+ import noImage from 'nitro-web/client/imgs/no-image.svg'
4
+ import avatarImg from 'nitro-web/client/imgs/avatar.jpg'
5
+ import { User } from 'types'
6
+
7
+ type AvatarProps = {
8
+ awsUrl: string
9
+ isRound?: boolean
10
+ user: User,
11
+ showPlaceholderImage?: boolean
12
+ className?: string
13
+ }
14
+
15
+ export function Avatar({ awsUrl, isRound, user, showPlaceholderImage, className }: AvatarProps) {
16
+ const classes = 'rounded-full w-[30px] h-[30px] object-cover transition-all duration-150 ease ' + (className || '')
17
+
18
+ function getInitials(user: User) {
19
+ const text = (user.firstName ? [user.firstName, user.lastName] : (user?.name||'').split(' ')).map((o) => o?.charAt(0))
20
+ if (text.length == 1) return text[0] || ''
21
+ if (text.length > 1) return `${text[0]}${text[text.length - 1]}`
22
+ return ''
23
+ }
24
+
25
+ function getHex(user: User) {
26
+ const colors = ['#067306', '#AA33FF', '#FF54AF', '#F44336', '#c03c3c', '#7775f2', '#d88c1b']
27
+ const charIndex = (user.firstName||'a').toLowerCase().charCodeAt(0) - 97
28
+ const charIndexLimited = (charIndex < 0 || charIndex > 25) ? 25 : charIndex
29
+ const index = Math.round(charIndexLimited / 25 * (colors.length-1))
30
+ return colors[index]
31
+ }
32
+
33
+ return (
34
+ user.avatar
35
+ ? <img class={classes} src={s3Image(awsUrl, user.avatar, 'small') || noImage} />
36
+ : showPlaceholderImage ? <img class={classes} src={avatarImg} width="30px" />
37
+ : <Initials className={classes} icon={{ initials: getInitials(user), hex: getHex(user) }} isRound={isRound} isMedium={true} />
38
+ )
39
+ }
40
+
@@ -1,16 +1,19 @@
1
- // todo: add loading indicator
2
1
  import { ChevronDownIcon } from '@heroicons/react/20/solid'
3
- /**
4
- * @param {'primary'|'secondary'|'white'|'primary-sm'|'secondary-sm'|'white-sm'} [type='primary']
5
- * @param {string} [className]
6
- * @param {boolean} [isLoading]
7
- * @param {React.ReactNode|string} [IconLeft]
8
- * @param {React.ReactNode|string} [IconRight]
9
- * @param {React.ReactNode|string} [IconRight2]
10
- * @param {React.ReactNode|string} [children]
11
- */
12
- export function Button({ color='primary', className, isLoading, IconLeft, IconRight, IconRight2, children, ...props }) {
13
- const size = color.match(/xs|sm|md|lg/) || 'md'
2
+
3
+ type Props = {
4
+ color?: 'primary'|'secondary'|'white'
5
+ size?: 'xs'|'sm'|'md'|'lg'
6
+ className?: string
7
+ isLoading?: boolean
8
+ IconLeft?: React.ReactNode|'v'
9
+ IconRight?: React.ReactNode|'v'
10
+ IconRight2?: React.ReactNode|'v'
11
+ children?: React.ReactNode|'v'
12
+ [key: string]: unknown
13
+ }
14
+
15
+ export function Button({ color='primary', size='md', className, isLoading, IconLeft, IconRight, IconRight2, children, ...props }: Props) {
16
+ // const size = (color.match(/xs|sm|md|lg/)?.[0] || 'md') as 'xs'|'sm'|'md'|'lg'
14
17
  const iconPosition = IconLeft ? 'left' : IconRight ? 'right' : IconRight2 ? 'right2' : 'none'
15
18
  const base = 'relative inline-block font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2'
16
19
 
@@ -34,15 +37,16 @@ export function Button({ color='primary', className, isLoading, IconLeft, IconRi
34
37
  right2: 'w-full inline-flex items-center justify-between',
35
38
  none: 'w-full ',
36
39
  }
37
-
38
- if (color.match(/primary/)) var colorAndSize = `${primary} ${sizes[size]}`
40
+
41
+ let colorAndSize = ''
42
+ if (color.match(/primary/)) colorAndSize = `${primary} ${sizes[size]}`
39
43
  else if (color.match(/secondary/)) colorAndSize = `${secondary} ${sizes[size]}`
40
44
  else if (color.match(/white/)) colorAndSize = `${white} ${sizes[size]}`
41
45
 
42
- var contentLayout = `${contentLayouts[iconPosition]}`
46
+ let contentLayout = `${contentLayouts[iconPosition]}`
43
47
  if (!(className||'').match(/gap-/)) contentLayout += ' gap-x-1.5'
44
48
 
45
- function getIcon(Icon, className) {
49
+ function getIcon(Icon: React.ReactNode|'v', className: string) {
46
50
  if (Icon == 'v') return <ChevronDownIcon className={className} />
47
51
  else return Icon
48
52
  }
@@ -1,30 +1,32 @@
1
1
  import { css } from 'twin.macro'
2
- import { cloneElement } from 'react'
3
- import { toArray } from '../../../util.js'
4
- import { forwardRef } from 'react'
5
- import { getSelectStyle } from '../form/select.jsx'
2
+ import { forwardRef, cloneElement } from 'react'
3
+ import { toArray } from 'nitro-web/util'
4
+ import { getSelectStyle } from 'nitro-web'
6
5
  import { CheckCircleIcon } from '@heroicons/react/24/solid'
7
6
 
7
+ type DropdownProps = {
8
+ animate?: boolean
9
+ children?: React.ReactNode
10
+ className?: string
11
+ css?: string
12
+ /** The direction of the menu **/
13
+ dir?: 'bottom-left'|'bottom-right'|'top-left'|'top-right'
14
+ options?: { label: string|React.ReactNode, onClick?: Function, isSelected?: boolean, icon?: React.ReactNode, className?: string }[]
15
+ /** Whether the dropdown is hoverable **/
16
+ isHoverable?: boolean
17
+ /** The minimum width of the menu **/
18
+ minWidth?: number | string
19
+ /** The content to render inside the top of the dropdown **/
20
+ menuChildren?: React.ReactNode
21
+ menuIsOpen?: boolean
22
+ menuToggles?: boolean
23
+ toggleCallback?: (isActive: boolean) => void
24
+ }
8
25
 
9
- /**
10
- * Dropdown component
11
- *
12
- * @param {boolean} animate
13
- * @param {React.ReactNode} children
14
- * @param {string} className
15
- * @param {'bottom-left'|'bottom-right'|'top-left'|'top-right'} [dir='bottom-left'] - The direction of the menu
16
- * @param {[{ label, onClick, isSelected, icon, className }]} options - Menu options
17
- * @param {boolean} isHoverable - Whether the dropdown is hoverable
18
- * @param {number} minWidth - The minimum width of the menu
19
- * @param {React.ReactNode} menuChildren - The content to render inside the top of the dropdown
20
- * @param {boolean} menuIsOpen - Whether the menu is open
21
- * @param {boolean} menuToggles - Whether the menu toggles
22
- * @param {function} toggleCallback - The callback function to call when the menu is toggled
23
- */
24
- export const Dropdown = forwardRef(function Dropdown({
26
+ export const Dropdown = forwardRef(function Dropdown({
25
27
  animate=true,
26
28
  children,
27
- className,
29
+ className,
28
30
  dir,
29
31
  options,
30
32
  isHoverable,
@@ -33,21 +35,21 @@ export const Dropdown = forwardRef(function Dropdown({
33
35
  menuIsOpen,
34
36
  menuToggles=true,
35
37
  toggleCallback,
36
- }, ref) {
38
+ }: DropdownProps, ref) {
37
39
  // https://letsbuildui.dev/articles/building-a-dropdown-menu-component-with-react-hooks
38
40
  isHoverable = isHoverable && !menuIsOpen
39
- const dropdownRef = useRef(null)
40
- const [isActive, setIsActive] = useState(menuIsOpen)
41
+ const dropdownRef = useRef<HTMLDivElement|null>(null)
42
+ const [isActive, setIsActive] = useState(!!menuIsOpen)
41
43
  const menuStyle = getSelectStyle({ name: 'menu', usePrefixes: true })
42
44
 
43
45
  // Expose the setIsActive function to the parent component
44
46
  useImperativeHandle(ref, () => ({ setIsActive }))
45
47
 
46
48
  useEffect(() => {
47
- const pageClick = (e) => {
49
+ const pageClick = (event: MouseEvent | FocusEvent) => {
48
50
  try {
49
51
  // If the active element exists and is clicked outside of the dropdown, toggle the dropdown
50
- if (dropdownRef.current !== null && !dropdownRef.current.contains(e.target)) setIsActive(!isActive)
52
+ if (dropdownRef.current !== null && !dropdownRef.current.contains(event.target as Node)) setIsActive(!isActive)
51
53
  } catch (_e) {
52
54
  // Errors throw for contains() when the user clicks off the webpage when open
53
55
  setIsActive(!isActive)
@@ -70,13 +72,13 @@ export const Dropdown = forwardRef(function Dropdown({
70
72
  if (toggleCallback) toggleCallback(isActive)
71
73
  }, [isActive])
72
74
 
73
- function onMouseDown(e) {
75
+ function onMouseDown(e: { key: string, preventDefault: Function }) {
74
76
  if (e.key && e.key != 'Enter') return
75
77
  if (e.key) e.preventDefault() // for button, stops buttons firing twice
76
78
  if (!isHoverable && !menuIsOpen && ((menuToggles || e.key) || !isActive)) setIsActive(!isActive)
77
79
  }
78
80
 
79
- function onClick(option, e) {
81
+ function onClick(option: { onClick?: Function }, e: React.MouseEvent) {
80
82
  if (option.onClick) option.onClick(e)
81
83
  if (!menuIsOpen) setIsActive(!isActive)
82
84
  }
@@ -114,7 +116,7 @@ export const Dropdown = forwardRef(function Dropdown({
114
116
  <li
115
117
  key={i}
116
118
  className={`${optionStyle} ${option.className}`}
117
- onClick={(e) => onClick(option, e)}
119
+ onClick={(e: React.MouseEvent) => onClick(option, e)}
118
120
  >
119
121
  <span class="flex-auto">{option.label}</span>
120
122
  { !!option.icon && option.icon }
@@ -128,7 +130,7 @@ export const Dropdown = forwardRef(function Dropdown({
128
130
  )
129
131
  })
130
132
 
131
- const style = () => css`
133
+ const style = css`
132
134
  ul {
133
135
  transition: transform 0.15s ease, opacity 0.15s ease, visibility 0s 0.15s ease;
134
136
  }
@@ -0,0 +1,16 @@
1
+ import GithubIcon from 'nitro-web/client/imgs/github.svg'
2
+
3
+ export function GithubLink({ filename }: { filename: string }) {
4
+ const base = 'https://github.com/boycce/nitro-web/blob/master/'
5
+ // Filenames are relative to the webpack start directory
6
+ // 1. Remove ../ from filename (i.e. for _example build)
7
+ // 2. Remove node_modules/nitro-web/ from filename (i.e. for packages using nitro-web)
8
+ const link = base + filename.replace(/^(\.\.\/|.*node_modules\/nitro-web\/)/, '')
9
+
10
+ return (
11
+ // <a href={link}>Go to Github</a>
12
+ <a href={link} className="fixed top-0 right-0">
13
+ <GithubIcon />
14
+ </a>
15
+ )
16
+ }
@@ -1,6 +1,15 @@
1
1
  import { css } from 'twin.macro'
2
2
 
3
- export function Initials({ icon, isBig, isMedium, isSmall, isRound, className }) {
3
+ type InitialsProps = {
4
+ icon?: { initials: string, hex: string }
5
+ isBig?: boolean
6
+ isMedium?: boolean
7
+ isSmall?: boolean
8
+ isRound?: boolean
9
+ className?: string
10
+ }
11
+
12
+ export function Initials({ icon, isBig, isMedium, isSmall, isRound, className }: InitialsProps) {
4
13
  return (
5
14
  <span
6
15
  css={style}
@@ -20,7 +29,7 @@ export function Initials({ icon, isBig, isMedium, isSmall, isRound, className })
20
29
  )
21
30
  }
22
31
 
23
- const style = (_theme) => css`
32
+ const style = css`
24
33
  // seen in input.jsx
25
34
  display: flex;
26
35
  align-items: center;