nitro-web 0.0.169 → 0.0.171

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client/index.ts CHANGED
@@ -23,7 +23,7 @@ export { Styleguide } from '../components/partials/styleguide'
23
23
  // Component Elements
24
24
  export { Accordion } from '../components/partials/element/accordion'
25
25
  export { Avatar } from '../components/partials/element/avatar'
26
- export { Button } from '../components/partials/element/button'
26
+ export { Button, Spinner } from '../components/partials/element/button'
27
27
  export { Calendar, type CalendarProps } from '../components/partials/element/calendar'
28
28
  export { Dropdown, type DropdownProps, type DropdownOption } from '../components/partials/element/dropdown'
29
29
  export { Filters, type FilterType, usePushChangesToPath } from '../components/partials/element/filters'
@@ -36,6 +36,7 @@ export { TimePicker, type TimePickerProps } from '../components/partials/element
36
36
  export { Tooltip } from '../components/partials/element/tooltip'
37
37
  export { Topbar } from '../components/partials/element/topbar'
38
38
  export { Table, type TableColumn, type TableProps, type TableRow, type TableRowType } from '../components/partials/element/table'
39
+ export { LoadingWithDots, LoadingOverlay } from '../components/partials/element/loading'
39
40
 
40
41
  // Component Form Elements
41
42
  export { Checkbox } from '../components/partials/form/checkbox'
@@ -41,16 +41,16 @@ export function Button({
41
41
 
42
42
  // Button colors, you can use custom colors by using className instead
43
43
  const colors = {
44
- 'primary': 'bg-primary hover:bg-primary-hover ring-transparent text-white [&>.loader]:border-white',
45
- 'secondary': 'bg-secondary hover:bg-secondary-hover ring-transparent text-white [&>.loader]:border-white',
46
- 'black': 'bg-black hover:bg-gray-800 ring-transparent text-white [&>.loader]:border-white',
47
- 'dark': 'bg-gray-800 hover:bg-gray-700 ring-transparent text-white [&>.loader]:border-white',
48
- 'white': 'bg-white hover:bg-gray-50 ring-gray-300 text-gray-900 [&>.loader]:border-black', // maybe change to text-foreground
49
- 'clear': 'hover:bg-gray-50 ring-gray-300 hover:text-foreground [&>.loader]:border-foreground !shadow-none',
50
- 'danger': 'bg-danger hover:bg-danger-hover ring-transparent text-white [&>.loader]:border-white',
51
- 'warning': 'bg-warning hover:bg-warning-hover ring-transparent text-white [&>.loader]:border-white',
52
- 'info': 'bg-info hover:bg-info-hover ring-transparent text-white [&>.loader]:border-white',
53
- 'success': 'bg-success hover:bg-success-hover ring-transparent text-white [&>.loader]:border-white',
44
+ 'primary': 'bg-primary hover:bg-primary-hover ring-transparent text-white [&>.spinner]:border-white',
45
+ 'secondary': 'bg-secondary hover:bg-secondary-hover ring-transparent text-white [&>.spinner]:border-white',
46
+ 'black': 'bg-black hover:bg-gray-800 ring-transparent text-white [&>.spinner]:border-white',
47
+ 'dark': 'bg-gray-800 hover:bg-gray-700 ring-transparent text-white [&>.spinner]:border-white',
48
+ 'white': 'bg-white hover:bg-gray-50 ring-gray-300 text-gray-900 [&>.spinner]:border-black', // maybe change to text-foreground
49
+ 'clear': 'hover:bg-gray-50 ring-gray-300 hover:text-foreground [&>.spinner]:border-foreground !shadow-none',
50
+ 'danger': 'bg-danger hover:bg-danger-hover ring-transparent text-white [&>.spinner]:border-white',
51
+ 'warning': 'bg-warning hover:bg-warning-hover ring-transparent text-white [&>.spinner]:border-white',
52
+ 'info': 'bg-info hover:bg-info-hover ring-transparent text-white [&>.spinner]:border-white',
53
+ 'success': 'bg-success hover:bg-success-hover ring-transparent text-white [&>.spinner]:border-white',
54
54
  }
55
55
 
56
56
  // Button sizes (px is better for height consistency)
@@ -91,12 +91,17 @@ export function Button({
91
91
  </span>
92
92
  {(IconRight || IconRightEnd) && getIcon(IconRight || IconRightEnd)}
93
93
  {
94
- isLoading &&
95
- <span className={
96
- 'loader !opacity-100 absolute top-[50%] left-[50%] w-[1rem] h-[1rem] ml-[-0.5rem] mt-[-0.5rem] ' +
97
- 'rounded-full animate-spin border-2 !border-t-transparent'
98
- } />
94
+ isLoading && <Spinner className={'!opacity-100 size-[1rem]'} absoluteCenter={true} />
99
95
  }
100
96
  </button>
101
97
  )
102
98
  }
99
+
100
+ export function Spinner({ className, absoluteCenter }: { className?: string, absoluteCenter?: boolean }) {
101
+ const absoluteCenterClass = absoluteCenter ? 'absolute top-[50%] left-[50%] ml-[-0.5rem] mt-[-0.5rem]' : ''
102
+ return (
103
+ <span
104
+ className={twMerge(`spinner border-black border-2 inline-block size-[1em] rounded-full animate-spin ${absoluteCenterClass} ${className||''} !border-t-transparent `)}
105
+ />
106
+ )
107
+ }
@@ -0,0 +1,25 @@
1
+ import { Spinner, twMerge } from 'nitro-web'
2
+
3
+ export function LoadingWithDots({ message='Loading', className, classNameDots }: {
4
+ message?: string,
5
+ className?: string,
6
+ classNameDots?: string
7
+ }) {
8
+ return (
9
+ <span className={`flex items-center gap-[0.2em] ${className}`}>
10
+ {message}<span className={twMerge('relative loading-dots', classNameDots)} />
11
+ </span>
12
+ )
13
+ }
14
+
15
+ export function LoadingOverlay({ message='Loading', className }: { message?: string, className?: string }) {
16
+ return (
17
+ <div
18
+ className={twMerge('absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center z-10 text-sm [&>span]:bg-white', className)}
19
+ >
20
+ <span className="inline-block flex items-center justify-center gap-3 p-2">
21
+ <Spinner />{message}
22
+ </span>
23
+ </div>
24
+ )
25
+ }
@@ -1,6 +1,6 @@
1
1
  import { JSX, useState, useCallback, Fragment, useMemo, useEffect } from 'react'
2
2
  import { ChevronDownIcon, ChevronUpIcon } from 'lucide-react'
3
- import { Checkbox, queryObject, queryString, twMerge } from 'nitro-web'
3
+ import { Checkbox, queryObject, queryString, Spinner, twMerge, LoadingWithDots, LoadingOverlay } from 'nitro-web'
4
4
  import { useLocation, useNavigate } from 'react-router-dom'
5
5
 
6
6
  export type TableRowType = 'row' | 'loading' | 'empty' | 'thead'
@@ -45,7 +45,11 @@ export type TableProps<T> = {
45
45
  columnHeaderClassName?: string
46
46
  checkboxClassName?: string
47
47
  checkboxSize?: number
48
+ loadingOverlayClassName?: string
48
49
  isLoading?:boolean
50
+ loadingMessage?: string
51
+ showLoadingInline?: boolean|JSX.Element
52
+ showLoadingOverlay?: boolean|JSX.Element
49
53
  }
50
54
 
51
55
  export function Table<T extends TableRow>({
@@ -71,7 +75,12 @@ export function Table<T extends TableRow>({
71
75
  columnHeaderClassName,
72
76
  checkboxClassName,
73
77
  checkboxSize=16,
78
+ loadingOverlayClassName,
79
+ // Loading
74
80
  isLoading=false,
81
+ loadingMessage,
82
+ showLoadingInline=false,
83
+ showLoadingOverlay=true,
75
84
  }: TableProps<T>) {
76
85
  const location = useLocation()
77
86
  const [selectedRowIds, setSelectedRowIds] = useState<string[]>([])
@@ -80,9 +89,9 @@ export function Table<T extends TableRow>({
80
89
  const [rand] = useState(() => new Date().getTime() + Math.random())
81
90
 
82
91
  const rowsToRender = useMemo(() => {
83
- // 1) Only show the first row when loading (content hidden), 2) an empty row when there are no records, or all rows
84
- return rows.length > 0 ? (isLoading ? rows.slice(0, 1) : rows) : [{ _id: '' }] as unknown as T[]
85
- }, [rows, isLoading])
92
+ // 1) Only show the first row when loading inline(content hidden), 2) an empty row when there are no records, or all rows
93
+ return rows.length > 0 ? ((isLoading && showLoadingInline) ? rows.slice(0, 1) : rows) : [{ _id: '' }] as unknown as T[]
94
+ }, [rows, isLoading, showLoadingInline])
86
95
 
87
96
  const columns = useMemo(() => {
88
97
  const checkboxCol: TableColumn = { value: 'checkbox', label: '', disableSort: true }
@@ -145,7 +154,7 @@ export function Table<T extends TableRow>({
145
154
  >
146
155
  <div
147
156
  style={{ borderSpacing: `0 ${rowGap}px` }}
148
- className={twMerge('table w-full border-separate', tableClassName)}
157
+ className={twMerge('table w-full border-separate', showLoadingOverlay ? 'relative' : '', tableClassName)}
149
158
  >
150
159
  {/* Thead row */}
151
160
  <div className="table-row relative">
@@ -181,7 +190,7 @@ export function Table<T extends TableRow>({
181
190
  >
182
191
  {
183
192
  col.value == 'checkbox'
184
- ? <>
193
+ ? <Fragment>
185
194
  <Checkbox
186
195
  size={checkboxSize}
187
196
  name={`checkbox-all-${rand}`}
@@ -195,7 +204,7 @@ export function Table<T extends TableRow>({
195
204
  >
196
205
  {generateCheckboxActions && generateCheckboxActions(selectedRowIds)}
197
206
  </div>
198
- </>
207
+ </Fragment>
199
208
  : <span className={twMerge(
200
209
  'flex items-center gap-x-2 transition-opacity',
201
210
  selectedRowIds.length ? 'opacity-0' : '',
@@ -267,9 +276,9 @@ export function Table<T extends TableRow>({
267
276
  />
268
277
  }
269
278
  {
270
- // Rows (content hidden when loading)
279
+ // Rows (content hidden when loading inline)
271
280
  row._id &&
272
- <div className={isLoading ? 'opacity-0 pointer-events-none' : ''}>
281
+ <div className={isLoading && showLoadingInline ? 'opacity-0 pointer-events-none' : ''}>
273
282
  {
274
283
  col.value == 'checkbox'
275
284
  ? <Checkbox
@@ -287,10 +296,21 @@ export function Table<T extends TableRow>({
287
296
  </div>
288
297
  }
289
298
  {
290
- // Show "loading" or "no records" text in the first column
291
- j == 0 && (isLoading || !row._id) &&
292
- <div className={'absolute top-0 h-full flex items-center justify-center text-sm text-gray-500'}>
293
- { isLoading ? <>Loading<span className="relative ml-[2px] loading-dots" /></> : 'No records found.' }
299
+ // Show "no records" or "loading" text in the first column
300
+ j == 0 && (!row._id || isLoading) &&
301
+ <div className={'absolute top-0 h-full flex items-center justify-center gap-3 text-sm text-gray-500'}>
302
+ {
303
+ (!row._id && !isLoading) ? (
304
+ 'No records found.'
305
+ ) : (!row._id && isLoading && showLoadingInline === true) ? (
306
+ <Fragment>
307
+ <Spinner className="border-gray-500" />
308
+ <LoadingWithDots message={loadingMessage} />
309
+ </Fragment>
310
+ ) : (!row._id && isLoading && showLoadingInline) ? (
311
+ showLoadingInline
312
+ ) : null
313
+ }
294
314
  </div>
295
315
  }
296
316
  </div>
@@ -302,6 +322,11 @@ export function Table<T extends TableRow>({
302
322
  )
303
323
  })
304
324
  }
325
+ {
326
+ (isLoading && showLoadingOverlay === true)
327
+ ? <LoadingOverlay className={twMerge('m-[1px]', loadingOverlayClassName)} message={loadingMessage} />
328
+ : (isLoading && showLoadingOverlay) ? showLoadingOverlay : null
329
+ }
305
330
  </div>
306
331
  </div>
307
332
  )
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Drop, Dropdown, Field, Select, Button as ButtonNitro, Checkbox, GithubLink, Modal, Calendar, injectedConfig, TimePicker,
3
- Filters, FilterType, Table, TableColumn, usePushChangesToPath,
3
+ Filters, FilterType, Table, TableColumn, usePushChangesToPath, Spinner, LoadingWithDots,
4
4
  } from 'nitro-web'
5
5
  import { date, getCurrencyOptions, onChange, ucFirst } from 'nitro-web/util'
6
6
  import { Check, EllipsisVerticalIcon, FileEditIcon } from 'lucide-react'
@@ -44,6 +44,7 @@ export function Styleguide({ className, elements, children, currencies }: Styleg
44
44
  'Filters',
45
45
  'Button Colors & Sizes',
46
46
  'Button Icons',
47
+ 'Loading Elements',
47
48
  'Varients',
48
49
  'Selects',
49
50
  'Inputs',
@@ -367,6 +368,18 @@ export function Styleguide({ className, elements, children, currencies }: Styleg
367
368
  </div>
368
369
  )}
369
370
 
371
+ {groups.includes('Loading Elements') && (
372
+ <div>
373
+ <h2 class="h3">Loading Elements</h2>
374
+ <div class="flex flex-wrap gap-x-6 gap-y-4 items-center mb-6">
375
+ <div><Spinner /></div>
376
+ <div><Spinner className="border-secondary" /></div>
377
+ <div><Spinner className="border-primary border-[3px]" /></div>
378
+ <div><LoadingWithDots message="Loading" /></div>
379
+ </div>
380
+ </div>
381
+ )}
382
+
370
383
  {groups.includes('Varients') && (
371
384
  <div>
372
385
  <h2 class="h3">Varients</h2>
@@ -659,6 +672,17 @@ export function Styleguide({ className, elements, children, currencies }: Styleg
659
672
  className="mb-6"
660
673
  isLoading={true}
661
674
  />
675
+ <Table
676
+ rows={[]}
677
+ columns={thead}
678
+ rowSideColor={(row) => ({ className: row?.status == 'pending' ? 'bg-yellow-400' : '', width: 5 })}
679
+ generateCheckboxActions={generateCheckboxActions}
680
+ generateTd={generateTd}
681
+ className="mb-6"
682
+ isLoading={true}
683
+ showLoadingOverlay={false}
684
+ showLoadingInline={true}
685
+ />
662
686
  <Table
663
687
  rows={rows.slice(0, 2).map(row => ({ ...row, _id: row._id + '1' }))}
664
688
  columns={thead}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitro-web",
3
- "version": "0.0.169",
3
+ "version": "0.0.171",
4
4
  "repository": "github:boycce/nitro-web",
5
5
  "homepage": "https://boycce.github.io/nitro-web/",
6
6
  "description": "Nitro is a battle-tested, modular base project to turbocharge your projects, styled using Tailwind 🚀",