nitro-web 0.0.85 → 0.0.87

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 (41) hide show
  1. package/client/globals.ts +10 -6
  2. package/package.json +14 -6
  3. package/types/{required-globals.d.ts → globals.d.ts} +3 -1
  4. package/.editorconfig +0 -9
  5. package/components/auth/auth.api.js +0 -410
  6. package/components/auth/reset.tsx +0 -86
  7. package/components/auth/signin.tsx +0 -76
  8. package/components/auth/signup.tsx +0 -62
  9. package/components/billing/stripe.api.js +0 -268
  10. package/components/dashboard/dashboard.tsx +0 -32
  11. package/components/partials/element/accordion.tsx +0 -102
  12. package/components/partials/element/avatar.tsx +0 -40
  13. package/components/partials/element/button.tsx +0 -98
  14. package/components/partials/element/calendar.tsx +0 -125
  15. package/components/partials/element/dropdown.tsx +0 -248
  16. package/components/partials/element/filters.tsx +0 -194
  17. package/components/partials/element/github-link.tsx +0 -16
  18. package/components/partials/element/initials.tsx +0 -66
  19. package/components/partials/element/message.tsx +0 -141
  20. package/components/partials/element/modal.tsx +0 -90
  21. package/components/partials/element/sidebar.tsx +0 -195
  22. package/components/partials/element/tooltip.tsx +0 -154
  23. package/components/partials/element/topbar.tsx +0 -15
  24. package/components/partials/form/checkbox.tsx +0 -150
  25. package/components/partials/form/drop-handler.tsx +0 -68
  26. package/components/partials/form/drop.tsx +0 -141
  27. package/components/partials/form/field-color.tsx +0 -86
  28. package/components/partials/form/field-currency.tsx +0 -158
  29. package/components/partials/form/field-date.tsx +0 -252
  30. package/components/partials/form/field.tsx +0 -231
  31. package/components/partials/form/form-error.tsx +0 -27
  32. package/components/partials/form/location.tsx +0 -225
  33. package/components/partials/form/select.tsx +0 -360
  34. package/components/partials/is-first-render.ts +0 -14
  35. package/components/partials/not-found.tsx +0 -7
  36. package/components/partials/styleguide.tsx +0 -407
  37. package/semver-updater.cjs +0 -13
  38. package/tsconfig.json +0 -38
  39. package/tsconfig.types.json +0 -15
  40. package/types/core-only-globals.d.ts +0 -9
  41. package/types.ts +0 -60
@@ -1,225 +0,0 @@
1
- // @ts-nocheck
2
- // todo: finish tailwind conversion
3
- import * as util from 'nitro-web/util'
4
-
5
- type LocationProps = {
6
- clear: boolean
7
- id?: string
8
- name: string
9
- onInput?: (place: Place) => void
10
- onSelect?: (place: Place) => void
11
- placeholder?: string
12
- placeTypes?: string[]
13
- value?: Place
14
- googleMapsApiKey: string
15
- }
16
-
17
- export function Location({ clear, id, name, onInput, onSelect, placeholder, placeTypes, value, googleMapsApiKey }: LocationProps) {
18
- /**
19
- * Get location or area of place (requires both 'maps javascript' and 'places' APIs)
20
- *
21
- * @param {boolean} clear - clear input after select
22
- * @param {function(place)} onInput - called when the input value changes, with an
23
- * empty place, e.g. {full: '...', fullModified: true}
24
- * @param {function(place)} onSelect - called when a place is selected
25
- * @param {object} value - {full, line1, ..etc}
26
- *
27
- * Handy box tester (see also util.mongoAddKmsToBox())
28
- * https://www.keene.edu/campus/maps/tool/
29
- *
30
- * Returned Google places viewport (area), i.e. `place.geometry.viewport`
31
- * {
32
- * Qa: {g: 174.4438160493033, h: 174.9684260722261} == [btmLng, topLng]
33
- * zb: {g: -37.05901990116617, h: -36.66060184426172} == [btmLat, topLat]
34
- * }
35
- */
36
- const inputRef = useRef(null)
37
- const full = (value || {}).full || ''
38
- const [inputValue, setInputValue] = useState(full)
39
-
40
- useEffect(() => {
41
- if (!onSelect) console.error('Please pass `onSelect` to location.jsx')
42
- let autoComplete
43
- loadGoogleMaps(googleMapsApiKey).then(() => {
44
- if (inputRef.current) {
45
- autoComplete = new window.google.maps.places.Autocomplete(inputRef.current, {
46
- types: placeTypes ? placeTypes : ['address'],
47
- componentRestrictions: { country: ['nz'] },
48
- })
49
- autoComplete.setFields(['address_components', 'formatted_address', 'geometry'])
50
- autoComplete.addListener('place_changed', onPlaceSelect)
51
- inputRef.current.addEventListener('keydown', onKeyDown)
52
- }
53
- })
54
- return () => {
55
- // It seems like autoComplete cleans up both listeners, handy links if needing to remove sooner..
56
- // Cleanup listners: https://stackoverflow.com/a/22862011/1900648
57
- // Cleanup .pac-container: https://stackoverflow.com/a/21419890/1900648
58
- for (const elem of document.getElementsByClassName('pac-container')) elem.remove()
59
- }
60
- }, [])
61
-
62
- useEffect(() => {
63
- if (full !== inputValue) setInputValue(full)
64
- }, [full])
65
-
66
- function formatAddressObject(place) {
67
- console.log(place)
68
- var addressMap = {
69
- city: ['locality'],
70
- country: ['country'],
71
- number: ['street_number'],
72
- postcode: ['postal_code'],
73
- region: [
74
- 'administrative_area_level_1',
75
- 'administrative_area_level_2',
76
- 'administrative_area_level_3',
77
- 'administrative_area_level_4',
78
- 'administrative_area_level_5',
79
- ],
80
- street: ['street_address', 'route'],
81
- suburb: [
82
- 'sublocality',
83
- 'sublocality_level_1',
84
- 'sublocality_level_2',
85
- 'sublocality_level_3',
86
- 'sublocality_level_4',
87
- ],
88
- unit: ['subpremise'],
89
- }
90
- var address = {
91
- city: '',
92
- country: '',
93
- number: '',
94
- postcode: '',
95
- region: '',
96
- street: '',
97
- suburb: '',
98
- unit: '',
99
- }
100
- place.address_components.forEach((component) => {
101
- for (var key in addressMap) {
102
- if (addressMap[key].indexOf(component.types[0]) !== -1) {
103
- address[key] = component.long_name
104
- }
105
- }
106
- })
107
- if (!address.city) {
108
- address.city = address.suburb
109
- address.suburb = ''
110
- }
111
- return address
112
- }
113
-
114
- function onPlaceSelect() {
115
- const place = this.getPlace()
116
- if (!place.geometry) return
117
- if (clear) setInputValue('')
118
- else setInputValue(place.formatted_address)
119
- const addressObject = formatAddressObject(place)
120
- onSelect({
121
- city: addressObject.city,
122
- country: addressObject.country,
123
- line1: [[addressObject.unit, addressObject.number].filter(o=>o).join('/'), addressObject.street].join(' '),
124
- line2: [addressObject.suburb, addressObject.postcode].filter(o=>o).join(', '),
125
- full: place.formatted_address,
126
- number: addressObject.number,
127
- postcode: addressObject.postcode,
128
- suburb: addressObject.suburb,
129
- location: {
130
- coordinates: [place.geometry.location.lng(), place.geometry.location.lat()],
131
- type: 'Point',
132
- },
133
- unit: addressObject.unit,
134
- area: !util.deepFind(place, 'geometry.viewport') ? undefined : {
135
- bottomLeft: [
136
- place.geometry.viewport.getSouthWest().lng(),
137
- place.geometry.viewport.getSouthWest().lat(),
138
- ],
139
- topRight: [
140
- place.geometry.viewport.getNorthEast().lng(),
141
- place.geometry.viewport.getNorthEast().lat(),
142
- ],
143
- },
144
- })
145
- }
146
-
147
- function onChange(event) {
148
- // On input change
149
- setInputValue(event.target.value)
150
- if (onInput) onInput({
151
- full: event.target.value,
152
- fullModified: true,
153
- })
154
- }
155
-
156
- function onFocus(event) {
157
- // Required to disable the chrome autocomplete, https://stackoverflow.com/a/57131179/4553162
158
- if (event.target.autocomplete) {
159
- event.target.autocomplete = 'off'
160
- }
161
- }
162
-
163
- function onKeyDown(event) {
164
- // Stop form submission if there is a google autocomplete dropdown opened
165
- let prevented
166
- if (event.key === 'Enter') {
167
- for (const el of document.getElementsByClassName('pac-container')) {
168
- if (el.offsetParent !== null && !prevented) { // google autocomplete opened somewhere
169
- event.preventDefault()
170
- prevented = true
171
- }
172
- }
173
- }
174
- }
175
-
176
- return (
177
- <input
178
- id={id||name}
179
- name={name||id}
180
- onChange={onChange}
181
- onFocus={onFocus}
182
- placeholder={placeholder}
183
- ref={inputRef}
184
- type="text"
185
- value={inputValue}
186
- />
187
- )
188
- }
189
-
190
- function loadGoogleMaps(googleMapsApiKey) {
191
- // Requires both 'maps javascript api' and 'places api' within Goolge Cloud Platform
192
- if (!window.initMap) {
193
- window.initMap = () => {/*noop to prevent warning*/}
194
- }
195
-
196
- return new Promise((res) => {
197
- const scriptId = 'googleMapsUrl'
198
- let script = document.getElementById(scriptId)
199
- // script not yet inserted
200
- if (script === null) {
201
- script = document.createElement('script')
202
- script.type = 'text/javascript'
203
- script.id = scriptId
204
- script.src = `https://maps.googleapis.com/maps/api/js?key=${googleMapsApiKey}`+
205
- '&libraries=places&callback=initMap'
206
- script.onload = () => res()
207
- document.getElementsByTagName('head')[0].appendChild(script)
208
- // script has already been inserted
209
- } else {
210
- // script has already loaded
211
- if (window.google) {
212
- res()
213
- // script hasn't been loaded yet
214
- } else {
215
- const cachedCallback = script.onload
216
- script.onload = () => {
217
- cachedCallback()
218
- res()
219
- }
220
- }
221
- }
222
- })
223
- }
224
-
225
- // Styles are in custom.css
@@ -1,360 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { css } from 'twin.macro'
3
- import { memo } from 'react'
4
- import ReactSelect, {
5
- components, ControlProps, createFilter, OptionProps, SingleValueProps, ClearIndicatorProps,
6
- DropdownIndicatorProps, MultiValueRemoveProps,
7
- } from 'react-select'
8
- import { CheckCircleIcon } from '@heroicons/react/20/solid'
9
- import { ChevronsUpDownIcon, XIcon } from 'lucide-react'
10
- import { isFieldCached } from 'nitro-web'
11
- import { getErrorFromState, deepFind, twMerge } from 'nitro-web/util'
12
- import { Errors } from 'nitro-web/types'
13
-
14
- const filterFn = createFilter()
15
-
16
- type GetSelectStyle = {
17
- name: string
18
- isFocused?: boolean
19
- isSelected?: boolean
20
- hasError?: boolean
21
- usePrefixes?: boolean
22
- }
23
-
24
- /** Select (all other props are passed to react-select) **/
25
- export type SelectProps = {
26
- /** field name or path on state (used to match errors), e.g. 'date', 'company.email' **/
27
- name: string
28
- /** inputId, the name is used if not provided **/
29
- id?: string
30
- /** 'container' id to pass to react-select **/
31
- containerId?: string
32
- /** The minimum width of the dropdown menu **/
33
- minMenuWidth?: number
34
- /** The prefix to add to the input **/
35
- prefix?: string
36
- /** The onChange handler **/
37
- onChange?: (event: { target: { name: string, value: unknown } }) => void
38
- /** The options to display in the dropdown **/
39
- options: { value: unknown, label: string | React.ReactNode, fixed?: boolean, [key: string]: unknown }[]
40
- /** The state object to get the value and check errors from **/
41
- state?: { errors?: Errors, [key: string]: any } // was unknown|unknown[]
42
- /** Select variations **/
43
- mode?: 'country'|'customer'|''
44
- /** Pass dependencies to break memoization, handy for onChange/onInputChange **/
45
- deps?: unknown[]
46
- /** title used to find related error messages */
47
- errorTitle?: string|RegExp
48
- /** All other props are passed to react-select **/
49
- [key: string]: unknown
50
- }
51
-
52
- export const Select = memo(SelectBase, (prev, next) => {
53
- return isFieldCached(prev, next)
54
- })
55
-
56
- function SelectBase({
57
- id, containerId, minMenuWidth, name, prefix='', onChange, options, state, mode='', errorTitle, ...props
58
- }: SelectProps) {
59
- let value: unknown|unknown[]
60
- const error = getErrorFromState(state, errorTitle || name)
61
- if (!name) throw new Error('Select component requires a `name` and `options` prop')
62
-
63
- // Get value from value or state
64
- if (typeof props.value !== 'undefined') value = props.value
65
- else if (typeof state == 'object') value = deepFind(state, name)
66
-
67
- // If multi-select, filter options by value
68
- if (Array.isArray(value)) value = options.filter(o => (value as unknown[]).includes(o.value))
69
- else value = options.find(o => value === o.value)
70
-
71
- // Input is always controlled if state is passed in
72
- if (typeof state == 'object' && typeof value == 'undefined') value = ''
73
-
74
- return (
75
- <div css={style} class={'mt-2.5 mb-6 ' + twMerge(`mt-input-before mb-input-after nitro-select ${props.className||''}`)}>
76
- <ReactSelect
77
- /**
78
- * react-select prop quick reference (https://react-select.com/props#api):
79
- * isDisabled={false}
80
- * isMulti={false}
81
- * isSearchable={true}
82
- * options={[{ value: 'chocolate', label: 'Chocolate' }]}
83
- * placeholder="Select a color"
84
- * value={options.find(o => o.code == state.color)} // to clear you need to set to null, not undefined
85
- * isClearable={false}
86
- * menuIsOpen={false}
87
- */
88
- {...props}
89
- // @ts-expect-error
90
- _nitro={{ prefix, mode }}
91
- key={value as string}
92
- unstyled={true}
93
- inputId={id || name}
94
- id={containerId}
95
- filterOption={(option, searchText) => {
96
- if ((option.data as {fixed?: boolean}).fixed) return true
97
- return filterFn(option, searchText)
98
- }}
99
- menuPlacement="auto"
100
- minMenuHeight={250}
101
- onChange={!onChange ? undefined : (o) => {
102
- // Array returned for multi-select
103
- const value = Array.isArray(o)
104
- ? o.map(v => typeof v == 'object' && v !== null && 'value' in v ? v.value : v)
105
- : (typeof o == 'object' && o !== null && 'value' in o ? o.value : o)
106
- return onChange({ target: { name: name, value: value }})
107
- }}
108
- options={options}
109
- value={value}
110
- classNames={{
111
- // Input container
112
- control: (p) => getSelectStyle({ name: 'control', hasError: !!error, ...p }),
113
- valueContainer: () => getSelectStyle({ name: 'valueContainer' }),
114
- // Input container objects
115
- input: () => getSelectStyle({ name: 'input', hasError: !!error }),
116
- multiValue: () => getSelectStyle({ name: 'multiValue' }),
117
- multiValueLabel: () => '',
118
- multiValueRemove: () => getSelectStyle({ name: 'multiValueRemove' }),
119
- placeholder: () => getSelectStyle({ name: 'placeholder' }),
120
- singleValue: () => getSelectStyle({ name: 'singleValue', hasError: !!error }),
121
- // Indicators
122
- clearIndicator: () => getSelectStyle({ name: 'clearIndicator' }),
123
- dropdownIndicator: () => getSelectStyle({ name: 'dropdownIndicator' }),
124
- indicatorsContainer: () => getSelectStyle({ name: 'indicatorsContainer' }),
125
- indicatorSeparator: () => getSelectStyle({ name: 'indicatorSeparator' }),
126
- // Dropmenu
127
- menu: () => getSelectStyle({ name: 'menu' }),
128
- groupHeading: () => getSelectStyle({ name: 'groupHeading' }),
129
- noOptionsMessage: () => getSelectStyle({ name: 'noOptionsMessage' }),
130
- option: (p) => getSelectStyle({ name: 'option', ...p }),
131
- }}
132
- components={{
133
- Control,
134
- SingleValue,
135
- Option,
136
- DropdownIndicator,
137
- ClearIndicator,
138
- MultiValueRemove,
139
- }}
140
- styles={{
141
- menu: (base) => ({
142
- ...base, minWidth: minMenuWidth,
143
- }),
144
- // On mobile, the label will truncate automatically, so we want to
145
- // override that behaviour.
146
- multiValueLabel: (base) => ({
147
- ...base,
148
- whiteSpace: 'normal',
149
- overflow: 'visible',
150
- }),
151
- control: (base) => ({
152
- ...base,
153
- outline: undefined,
154
- transition: 'none',
155
- }),
156
- }}
157
- // menuIsOpen={true}
158
- // isSearchable={false}
159
- // isClearable={true}
160
- // isMulti={true}
161
- // isDisabled={true}
162
- // maxMenuHeight={200}
163
- />
164
- {error && <div class="mt-1.5 text-xs text-danger-foreground">{error.detail}</div>}
165
- </div>
166
- )
167
- }
168
-
169
- function Control({ children, ...props }: ControlProps) {
170
- // Add flag and prefix to the input (control)
171
- // todo: check that the flag/prefix looks okay
172
- const selectedOption = props.getValue()[0]
173
- const optionFlag = (selectedOption as { flag?: string })?.flag
174
- const _nitro = (props.selectProps as { _nitro?: { prefix?: string, mode?: string } })?._nitro
175
- return (
176
- <components.Control {...props}>
177
- {
178
- (() => {
179
- if (_nitro?.prefix) {
180
- return (
181
- <>
182
- <span class="relative right-[2px]">{_nitro?.prefix}</span>
183
- {children}
184
- </>
185
- )
186
- } else if (_nitro?.mode == 'country') {
187
- return (
188
- <>
189
- { optionFlag && <Flag flag={optionFlag} /> }
190
- {children}
191
- </>
192
- )
193
- } else {
194
- return children
195
- }
196
- })()
197
- }
198
- </components.Control>
199
- )
200
- }
201
-
202
- function SingleValue(props: SingleValueProps) {
203
- const selectedOption = props.getValue()[0] as { labelControl?: string }
204
- return (
205
- <components.SingleValue {...props}>
206
- <>{selectedOption?.labelControl || props.children}</>
207
- </components.SingleValue>
208
- )
209
- }
210
-
211
- function Option(props: OptionProps) {
212
- // todo: check that the flag looks okay
213
- const data = props.data as { className?: string, flag?: string }
214
- const _nitro = (props.selectProps as { _nitro?: { mode?: string } })?._nitro
215
- return (
216
- <components.Option className={data.className} {...props}>
217
- { _nitro?.mode == 'country' && <Flag flag={data.flag} /> }
218
- <span class="flex-auto">{props.label}</span>
219
- {props.isSelected && <CheckCircleIcon className="size-[22px] text-primary -my-1 -mx-0.5" />}
220
- </components.Option>
221
- )
222
- }
223
-
224
- const DropdownIndicator = (props: DropdownIndicatorProps) => {
225
- return (
226
- <components.DropdownIndicator {...props}>
227
- <ChevronsUpDownIcon size={15} className="text-gray-400 -my-0.5 -mx-[1px]" />
228
- </components.DropdownIndicator>
229
- )
230
- }
231
-
232
- const ClearIndicator = (props: ClearIndicatorProps) => {
233
- return (
234
- <components.ClearIndicator {...props}>
235
- <XIcon size={14} />
236
- </components.ClearIndicator>
237
- )
238
- }
239
-
240
- const MultiValueRemove = (props: MultiValueRemoveProps) => {
241
- return (
242
- <components.MultiValueRemove {...props}>
243
- <XIcon className="size-[1em] p-[1px]" />
244
- </components.MultiValueRemove>
245
- )
246
- }
247
-
248
- function Flag({ flag }: { flag?: string }) {
249
- if (!flag) return null
250
- // todo: public needs to come from webpack
251
- const publicPath = '/'
252
- return (
253
- <span class="flag" style={{ backgroundImage: `url(${publicPath}assets/imgs/flags/${flag}.svg)` }} />
254
- )
255
- }
256
-
257
- const selectStyles = {
258
- // Based off https://www.jussivirtanen.fi/writing/styling-react-select-with-tailwind
259
- // Input container
260
- control: {
261
- base: 'rounded-md bg-white hover:cursor-pointer text-input-base outline outline-1 -outline-offset-1 '
262
- + '!min-h-0 outline-input-border',
263
- focus: 'outline-2 -outline-offset-2 outline-input-border-focus',
264
- error: 'outline-danger',
265
- },
266
- valueContainer: 'py-[9px] px-[12px] py-input-y px-input-x gap-1', // dont twMerge (input-x is optional)
267
- // Input container objects
268
- input: {
269
- base: 'text-input',
270
- error: 'text-danger-foreground',
271
- },
272
- multiValue: 'bg-primary text-white rounded items-center pl-2 pr-1.5 gap-1.5',
273
- multiValueLabel: 'text-xs',
274
- multiValueRemove: 'border border-black/10 bg-clip-content bg-white rounded-md text-foreground hover:bg-red-50',
275
- placeholder: 'text-input-placeholder',
276
- singleValue: {
277
- base: 'text-input',
278
- error: 'text-danger-foreground',
279
- },
280
- // Icon indicators
281
- clearIndicator: 'text-gray-500 p-1 rounded-md hover:bg-red-50 hover:text-danger-foreground',
282
- dropdownIndicator: 'p-1 hover:bg-gray-100 text-gray-500 rounded-md hover:text-black',
283
- indicatorsContainer: 'p-1 px-2 gap-1',
284
- indicatorSeparator: 'py-0.5 before:content-[""] before:block before:bg-gray-100 before:w-px before:h-full',
285
- // Dropdown menu
286
- menu: 'mt-1.5 border border-dropdown-ul-border bg-white rounded-md text-input-base overflow-hidden shadow-dropdown-ul',
287
- groupHeading: 'ml-3 mt-2 mb-1 text-gray-500 text-input-base',
288
- noOptionsMessage: 'm-1 text-gray-500 p-2 bg-gray-50 border border-dashed border-gray-200 rounded-sm',
289
- option: {
290
- base: 'relative px-3 py-2 !flex items-center gap-2 cursor-default',
291
- hover: 'bg-gray-50',
292
- selected: '!bg-gray-100 text-dropdown-selected-foreground',
293
- },
294
- }
295
-
296
- export function getSelectStyle({ name, isFocused, isSelected, hasError, usePrefixes }: GetSelectStyle) {
297
- // Returns a class list that conditionally includes hover/focus modifier classes, or uses CSS modifiers, e.g. hover:, focus:
298
- // @ts-expect-error
299
- const obj = selectStyles[name]
300
- let output = obj?.base
301
- if (typeof obj == 'string') return obj // no modifiers
302
-
303
- if (usePrefixes) {
304
- if (obj.focus) output += ' ' + obj.focus.split(' ').map((part: string) => `focus:${part}`).join(' ')
305
- if (obj.hover) output += ' ' + obj.hover.split(' ').map((part: string) => `hover:${part}`).join(' ')
306
- } else {
307
- if (obj.focus && isFocused) output += ` ${obj.focus}`
308
- if (obj.hover && isFocused) output += ` ${obj.hover}`
309
- }
310
- if (obj.error && hasError) output += ` ${obj.error}`
311
- if (obj.selected && isSelected) output += ` ${obj.selected}`
312
-
313
- return twMerge(output)
314
- }
315
-
316
- const style = css`
317
- /*
318
- todo: add these as tailwind classes
319
-
320
- &.rs-medium {
321
- .rs__control {
322
- padding: 9px 13px;
323
- font-size: 13px;
324
- font-weight: 400;
325
- min-height: 0;
326
- }
327
- .rs__menu {
328
- .rs__option {
329
- font-size: 0.85rem;
330
- }
331
- }
332
- }
333
- &.rs-small {
334
- .rs__control {
335
- padding: 5px 13px;
336
- font-size: 12.5px;
337
- font-weight: 400;
338
- min-height: 0;
339
- }
340
- .rs__menu {
341
- .rs__option {
342
- font-size: 0.8rem;
343
- }
344
- }
345
- } */
346
-
347
- /*
348
- .flag {
349
- // https://github.com/lipis/flag-icons
350
- flex-shrink: 0;
351
- margin-right: 10px;
352
- width: 21px;
353
- height: 14px;
354
- background-size: cover;
355
- background-repeat: no-repeat;
356
- background-position: center;
357
- border-radius: 3px;
358
- overflow: hidden;
359
- }*/
360
- `
@@ -1,14 +0,0 @@
1
- export function IsFirstRender(delay?: number) {
2
- /*
3
- * Checks if the current render of a react component is the first
4
- * E.g. const isFirst = isFirstRender()
5
- * @link https://stackoverflow.com/a/56267719/1900648
6
- * @return boolean
7
- */
8
- const isMountRef = useRef(true)
9
- useEffect(() => {
10
- if (delay) setTimeout(() => isMountRef.current = false, delay)
11
- else isMountRef.current = false
12
- }, [])
13
- return isMountRef.current
14
- }
@@ -1,7 +0,0 @@
1
- export function NotFound() {
2
- return (
3
- <div class="pt-14 text-center" style={{'minHeight': '300px'}}>
4
- Sorry, nothing found.
5
- </div>
6
- )
7
- }