nitro-web 0.0.102 → 0.0.104

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
@@ -34,6 +34,7 @@ export { Modal } from '../components/partials/element/modal'
34
34
  export { Sidebar, type SidebarProps } from '../components/partials/element/sidebar'
35
35
  export { Tooltip } from '../components/partials/element/tooltip'
36
36
  export { Topbar } from '../components/partials/element/topbar'
37
+ export { Table, type TableColumn, type TableProps, type TableRow } from '../components/partials/element/table'
37
38
 
38
39
  // Component Form Elements
39
40
  export { Checkbox } from '../components/partials/form/checkbox'
@@ -1,12 +1,21 @@
1
1
  import { Topbar, Field, FormError, Button, request, onChange } from 'nitro-web'
2
2
  import { Errors } from 'nitro-web/types'
3
3
 
4
- export function ResetInstructions({ className }: { className?: string }) {
4
+ type resetInstructionsProps = {
5
+ className?: string,
6
+ elements?: { Button?: typeof Button },
7
+ }
8
+
9
+ export function ResetInstructions({ className, elements }: resetInstructionsProps) {
5
10
  const navigate = useNavigate()
6
11
  const isLoading = useState(false)
7
12
  const [, setStore] = useTracked()
8
13
  const [state, setState] = useState({ email: '', errors: [] as Errors })
9
14
 
15
+ const Elements = {
16
+ Button: elements?.Button || Button,
17
+ }
18
+
10
19
  async function onSubmit (event: React.FormEvent<HTMLFormElement>) {
11
20
  try {
12
21
  await request('post /api/reset-instructions', state, event, isLoading, setState)
@@ -32,13 +41,13 @@ export function ResetInstructions({ className }: { className?: string }) {
32
41
  <FormError state={state} className="pt-2" />
33
42
  </div>
34
43
 
35
- <Button className="w-full" isLoading={isLoading[0]} type="submit">Email me a reset password link</Button>
44
+ <Elements.Button className="w-full" isLoading={isLoading[0]} type="submit">Email me a reset password link</Elements.Button>
36
45
  </form>
37
46
  </div>
38
47
  )
39
48
  }
40
49
 
41
- export function ResetPassword({ className }: { className?: string }) {
50
+ export function ResetPassword({ className, elements }: resetInstructionsProps) {
42
51
  const navigate = useNavigate()
43
52
  const params = useParams()
44
53
  const isLoading = useState(false)
@@ -49,6 +58,10 @@ export function ResetPassword({ className }: { className?: string }) {
49
58
  token: params.token,
50
59
  errors: [] as Errors,
51
60
  }))
61
+
62
+ const Elements = {
63
+ Button: elements?.Button || Button,
64
+ }
52
65
 
53
66
  async function onSubmit (event: React.FormEvent<HTMLFormElement>) {
54
67
  try {
@@ -79,7 +92,7 @@ export function ResetPassword({ className }: { className?: string }) {
79
92
  <FormError state={state} className="pt-2" />
80
93
  </div>
81
94
 
82
- <Button class="w-full" isLoading={isLoading[0]} type="submit">Reset Password</Button>
95
+ <Elements.Button class="w-full" isLoading={isLoading[0]} type="submit">Reset Password</Elements.Button>
83
96
  </form>
84
97
  </div>
85
98
  )
@@ -1,7 +1,12 @@
1
1
  import { Topbar, Field, Button, FormError, request, queryObject, injectedConfig, updateJwt, onChange } from 'nitro-web'
2
2
  import { Errors } from 'nitro-web/types'
3
3
 
4
- export function Signin({ className }: { className?: string }) {
4
+ type signinProps = {
5
+ className?: string,
6
+ elements?: { Button?: typeof Button },
7
+ }
8
+
9
+ export function Signin({ className, elements }: signinProps) {
5
10
  const navigate = useNavigate()
6
11
  const location = useLocation()
7
12
  const isSignout = location.pathname == '/signout'
@@ -12,6 +17,10 @@ export function Signin({ className }: { className?: string }) {
12
17
  password: injectedConfig.env == 'development' ? '1234' : '',
13
18
  errors: [] as Errors,
14
19
  })
20
+
21
+ const Elements = {
22
+ Button: elements?.Button || Button,
23
+ }
15
24
 
16
25
  useEffect(() => {
17
26
  // Autofill the email input from ?email=
@@ -69,7 +78,7 @@ export function Signin({ className }: { className?: string }) {
69
78
  <FormError state={state} className="pt-2" />
70
79
  </div>
71
80
 
72
- <Button class="w-full" isLoading={isLoading[0]} type="submit">Sign In</Button>
81
+ <Elements.Button class="w-full" isLoading={isLoading[0]} type="submit">Sign In</Elements.Button>
73
82
  </form>
74
83
  </div>
75
84
  )
@@ -1,7 +1,12 @@
1
1
  import { Button, Field, FormError, Topbar, request, injectedConfig, onChange } from 'nitro-web'
2
2
  import { Errors } from 'nitro-web/types'
3
3
 
4
- export function Signup({ className }: { className?: string }) {
4
+ type signupProps = {
5
+ className?: string,
6
+ elements?: { Button?: typeof Button },
7
+ }
8
+
9
+ export function Signup({ className, elements }: signupProps) {
5
10
  const navigate = useNavigate()
6
11
  const isLoading = useState(false)
7
12
  const [, setStore] = useTracked()
@@ -13,6 +18,10 @@ export function Signup({ className }: { className?: string }) {
13
18
  errors: [] as Errors,
14
19
  })
15
20
 
21
+ const Elements = {
22
+ Button: elements?.Button || Button,
23
+ }
24
+
16
25
  async function onSubmit (e: React.FormEvent<HTMLFormElement>) {
17
26
  try {
18
27
  const data = await request('post /api/signup', state, e, isLoading, setState)
@@ -55,7 +64,7 @@ export function Signup({ className }: { className?: string }) {
55
64
  <FormError state={state} className="pt-2" />
56
65
  </div>
57
66
 
58
- <Button class="w-full" isLoading={isLoading[0]} type="submit">Create Account</Button>
67
+ <Elements.Button class="w-full" isLoading={isLoading[0]} type="submit">Create Account</Elements.Button>
59
68
  </form>
60
69
  </div>
61
70
  )
@@ -0,0 +1,291 @@
1
+ import { JSX, useState, useCallback } from 'react'
2
+ import { ChevronDownIcon, ChevronUpIcon } from 'lucide-react'
3
+ import { Checkbox, queryObject, queryString, twMerge } from 'nitro-web'
4
+
5
+ export interface TableColumn {
6
+ label: string
7
+ value: string
8
+ className?: string
9
+ disableSort?: boolean
10
+ innerClassName?: string
11
+ minWidth?: number
12
+ overflow?: boolean
13
+ rowLinesMax?: number
14
+ /** Use if the value is different from the sortBy */
15
+ sortByValue?: string
16
+ align?: 'left' | 'center' | 'right'
17
+ }
18
+
19
+ export type TableRow = {
20
+ _id?: string
21
+ }
22
+
23
+ export type TableProps<T> = {
24
+ columns: TableColumn[]
25
+ rows: T[]
26
+ generateTd: (col: TableColumn, row: T, i: number, isLast: boolean) => JSX.Element | null
27
+ generateCheckboxActions?: (selectedRowIds: string[]) => JSX.Element | null
28
+ headerHeightMin?: number
29
+ rowHeightMin?: number
30
+ rowContentHeightMax?: number
31
+ rowLinesMax?: number
32
+ rowSideColor?: (row?: T) => { className: string, width: number }
33
+ rowSpacing?: number
34
+ // columnSpacing?: number
35
+ className?: string
36
+ tableClassName?: string
37
+ columnClassName?: string
38
+ columnHeaderClassName?: string
39
+ checkboxClassName?: string
40
+ checkboxSize?: number
41
+ }
42
+
43
+ export function Table<T extends TableRow>({
44
+ rows,
45
+ columns: columnsProp,
46
+ generateTd,
47
+ generateCheckboxActions,
48
+ headerHeightMin=40,
49
+ rowHeightMin=48,
50
+ rowContentHeightMax,
51
+ rowLinesMax,
52
+ rowSideColor,
53
+ rowSpacing=0,
54
+ // columnSpacing=15,
55
+ className,
56
+ tableClassName,
57
+ columnClassName,
58
+ columnHeaderClassName,
59
+ checkboxClassName,
60
+ checkboxSize=16,
61
+ }: TableProps<T>) {
62
+ const location = useLocation()
63
+ const [selectedRowIds, setSelectedRowIds] = useState<string[]>([])
64
+ const _columnClassName = 'table-cell px-3 py-1 align-middle text-sm border-y border-border ' +
65
+ 'first:border-l last:border-r border-t-0 box-border'
66
+
67
+ const columns = useMemo(() => {
68
+ const checkboxCol: TableColumn = { value: 'checkbox', label: '', disableSort: true }
69
+ const cols = (generateCheckboxActions ? [checkboxCol, ...columnsProp] : columnsProp).map((col, _i) => ({
70
+ ...col,
71
+ rowLinesMax: typeof col.rowLinesMax != 'undefined' ? col.rowLinesMax : rowLinesMax,
72
+ sortByValue: col.sortByValue || col.value,
73
+ align: col.align || 'left',
74
+ }))
75
+ return cols
76
+ }, [columnsProp])
77
+
78
+ const onSelect = useCallback((idOrAll: string, checked: boolean) => {
79
+ setSelectedRowIds((o) => {
80
+ if (idOrAll == 'all' && checked) return rows.map(row => row?._id||'')
81
+ else if (idOrAll == 'all' && !checked) return []
82
+ else if (o.includes(idOrAll) && !checked) return o.filter(id => id != idOrAll)
83
+ else if (!o.includes(idOrAll) && checked) return [...o, idOrAll]
84
+ else return o
85
+ })
86
+ }, [selectedRowIds, rows])
87
+
88
+ const getAlignClass = useCallback((align: TableColumn['align'], _returnJustify?: boolean) => {
89
+ if (_returnJustify) return align == 'left' ? '' : align == 'center' ? 'justify-center' : 'justify-end'
90
+ else return align == 'left' ? '' : align == 'center' ? 'text-center' : 'text-right'
91
+ }, [])
92
+
93
+ // Reset selected rows when the location changes
94
+ useEffect(() => setSelectedRowIds([]), [location.key])
95
+
96
+ // --- Sorting ---
97
+
98
+ const navigate = useNavigate()
99
+ const query = useMemo(() => ({ ...queryObject(location.search) }), [location.search])
100
+ const sortBy = useMemo(() => query.sortBy || 'createdAt', [query.sortBy])
101
+ const sort = useMemo(() => !query.sort && query.sortBy == 'createdAt' ? '-1' : (query.sort || '1'), [query.sort])
102
+
103
+ const onSort = useCallback((item: TableColumn) => {
104
+ const queryStr = queryString({
105
+ ...query,
106
+ sort: sortBy == item.sortByValue ? (sort == '1' ? '-1' : '1') : '1',
107
+ sortBy: item.sortByValue,
108
+ })
109
+ navigate(location.pathname + queryStr, { replace: true })
110
+ }, [location.pathname, query, sort, sortBy])
111
+
112
+ return (
113
+ <div
114
+ style={{ marginTop: -rowSpacing }}
115
+ className={twMerge('overflow-x-auto thin-scrollbar', className)}
116
+ >
117
+ <div
118
+ style={{ borderSpacing: `0 ${rowSpacing}px` }}
119
+ className={twMerge('table w-full border-separate', tableClassName)}
120
+ >
121
+ {/* Thead row */}
122
+ <div className="table-row relative">
123
+ {
124
+ columns.map((col, j) => {
125
+ const disableSort = col.disableSort || selectedRowIds.length
126
+ const sideColor = j == 0 && rowSideColor ? rowSideColor(undefined) : undefined
127
+ return (
128
+ <div
129
+ key={j}
130
+ onClick={disableSort ? undefined : () => onSort(col)}
131
+ style={{ height: headerHeightMin, minWidth: col.minWidth }}
132
+ className={twMerge(
133
+ _columnClassName,
134
+ 'h-auto text-sm font-medium border-t-1',
135
+ disableSort ? '' : 'cursor-pointer select-none',
136
+ getAlignClass(col.align),
137
+ columnClassName,
138
+ columnHeaderClassName,
139
+ col.className
140
+ )}
141
+ >
142
+ <div
143
+ style={{
144
+ maxHeight: rowContentHeightMax,
145
+ paddingLeft: sideColor && rows.length > 0 ? sideColor.width + 5 : 0,
146
+ }}
147
+ className={twMerge(
148
+ rowContentHeightMax ? 'overflow-hidden' : '',
149
+ getLineClampClassName(col.rowLinesMax),
150
+ col.overflow ? 'overflow-visible' : '',
151
+ col.innerClassName
152
+ )}
153
+ >
154
+ {
155
+ col.value == 'checkbox'
156
+ ? <>
157
+ <Checkbox
158
+ size={checkboxSize}
159
+ name="checkbox-all"
160
+ className='!m-0'
161
+ checkboxClassName={twMerge('border-foreground shadow-[0_1px_2px_0px_#0000001c]', checkboxClassName)}
162
+ onChange={(e) => onSelect('all', e.target.checked)}
163
+ />
164
+ <div
165
+ className={`${selectedRowIds.length ? 'block' : 'hidden'} [&>*]:absolute [&>*]:inset-y-0 [&>*]:left-[68px] [&>*]:z-10`}
166
+ >
167
+ {generateCheckboxActions && generateCheckboxActions(selectedRowIds)}
168
+ </div>
169
+ </>
170
+ : <span className={twMerge(
171
+ 'flex items-center gap-x-2 transition-opacity',
172
+ selectedRowIds.length ? 'opacity-0' : '',
173
+ getAlignClass(col.align, true)
174
+ )}>
175
+ <span>{col.label}</span>
176
+ {
177
+ (!col.disableSort && sortBy == col.sortByValue)
178
+ ? (sort == '1'
179
+ ? <ChevronDownIcon class='shrink-0 size-[16px]' />
180
+ : <ChevronUpIcon class='shrink-0 size-[16px]' />
181
+ )
182
+ : col.align == 'left' && <div class='size-[16px] shrink-0' /> // prevent layout shift on sort
183
+ }
184
+ </span>
185
+ }
186
+ </div>
187
+ </div>
188
+ )
189
+ })
190
+ }
191
+ </div>
192
+ {/* Tbody rows */}
193
+ {
194
+ rows.map((row: T, i: number) => {
195
+ return (
196
+ <div
197
+ key={`${row._id}-${i}`}
198
+ className={`table-row relative ${selectedRowIds.includes(row?._id||'') ? 'bg-gray-50' : 'bg-white'}`}
199
+ >
200
+ {
201
+ columns.map((col, j) => {
202
+ const sideColor = j == 0 && rowSideColor ? rowSideColor(row) : undefined
203
+ return (
204
+ <div
205
+ key={j}
206
+ style={{ height: rowHeightMin }}
207
+ className={twMerge(
208
+ _columnClassName,
209
+ getAlignClass(col.align),
210
+ columnClassName,
211
+ col.className
212
+ )}
213
+ >
214
+ <div
215
+ style={{
216
+ maxHeight: rowContentHeightMax,
217
+ paddingLeft: sideColor ? sideColor.width + 5 : 0,
218
+ }}
219
+ className={twMerge(
220
+ rowContentHeightMax ? 'overflow-hidden' : '',
221
+ getLineClampClassName(col.rowLinesMax),
222
+ col.overflow ? 'overflow-visible' : '',
223
+ col.innerClassName
224
+ )}
225
+ >
226
+ {
227
+ // Side color
228
+ sideColor &&
229
+ <div
230
+ className={`absolute top-0 left-0 h-full ${sideColor?.className||''}`}
231
+ style={{ width: sideColor.width }}
232
+ />
233
+ }
234
+ {
235
+ col.value == 'checkbox'
236
+ ? <Checkbox
237
+ size={checkboxSize}
238
+ name={`checkbox-${row._id}`}
239
+ onChange={(e) => onSelect(row?._id || '', e.target.checked)}
240
+ checked={selectedRowIds.includes(row?._id || '')}
241
+ className='!m-0'
242
+ checkboxClassName={twMerge('border-foreground shadow-[0_1px_2px_0px_#0000001c]', checkboxClassName)}
243
+ />
244
+ : generateTd(col, row, i, i == rows.length - 1)
245
+ }
246
+ </div>
247
+ </div>
248
+ )
249
+ })
250
+ }
251
+ </div>
252
+ )
253
+ })
254
+ }
255
+ {
256
+ rows.length == 0 &&
257
+ <div className='table-row relative'>
258
+ {
259
+ columns.map((col, j) => (
260
+ <div
261
+ key={j}
262
+ style={{ height: rowHeightMin }}
263
+ className={twMerge(_columnClassName, columnClassName, col.className)}
264
+ >
265
+ <div
266
+ className={twMerge(
267
+ 'absolute top-0 h-full flex items-center justify-center text-sm text-gray-500',
268
+ col.innerClassName
269
+ )}
270
+ >
271
+ { j == 0 && 'No records found.' }
272
+ </div>
273
+ </div>
274
+ ))
275
+ }
276
+ </div>
277
+ }
278
+ </div>
279
+ </div>
280
+ )
281
+ }
282
+
283
+ function getLineClampClassName(num?: number) {
284
+ // Splayed out for tailwind to pick up we are using the classNames below
285
+ if (num == 1) return 'line-clamp-1'
286
+ else if (num == 2) return 'line-clamp-2'
287
+ else if (num == 3) return 'line-clamp-3'
288
+ else if (num == 4) return 'line-clamp-4'
289
+ else if (num == 5) return 'line-clamp-5'
290
+ else if (num == 6) return 'line-clamp-6'
291
+ }
@@ -1,19 +1,36 @@
1
1
  import {
2
2
  Drop, Dropdown, Field, Select, Button as ButtonNitro, Checkbox, GithubLink, Modal, Calendar, injectedConfig,
3
- Filters, FiltersHandleType, FilterType,
3
+ Filters, FiltersHandleType, FilterType, Table, TableColumn,
4
4
  } from 'nitro-web'
5
- import { getCountryOptions, getCurrencyOptions, onChange, ucFirst } from 'nitro-web/util'
6
- import { Check, FileEditIcon } from 'lucide-react'
5
+ import { date, getCountryOptions, getCurrencyOptions, onChange, ucFirst } from 'nitro-web/util'
6
+ import { Check, EllipsisVerticalIcon, FileEditIcon } from 'lucide-react'
7
+
8
+ const perPage = 10
9
+ const statusColors = function(status: string) {
10
+ return {
11
+ pending: 'bg-yellow-400',
12
+ approved: 'bg-green-400',
13
+ rejected: 'bg-red-400',
14
+ }[status]
15
+ }
7
16
 
8
17
  type StyleguideProps = {
9
18
  className?: string
10
- elements?: {
11
- Button?: typeof ButtonNitro
12
- }
19
+ elements?: { Button?: typeof ButtonNitro }
13
20
  children?: React.ReactNode
14
21
  }
15
22
 
23
+ type QuoteExample = {
24
+ _id?: string
25
+ freightType: string
26
+ destination: { code: string }
27
+ date: number
28
+ weight: number
29
+ status: string
30
+ }
31
+
16
32
  export function Styleguide({ className, elements, children }: StyleguideProps) {
33
+ const Button = elements?.Button || ButtonNitro
17
34
  const [customerSearch, setCustomerSearch] = useState('')
18
35
  const [showModal1, setShowModal1] = useState(false)
19
36
  const [state, setState] = useState({
@@ -28,10 +45,12 @@ export function Styleguide({ className, elements, children }: StyleguideProps) {
28
45
  'date-time': Date.now(),
29
46
  calendar: [Date.now(), Date.now() + 1000 * 60 * 60 * 24 * 8],
30
47
  firstName: 'Bruce',
48
+ tableFilter: '',
31
49
  errors: [
32
50
  { title: 'address', detail: 'Address is required' },
33
51
  ],
34
52
  })
53
+
35
54
  const [filterState, setFilterState] = useState({})
36
55
  const filtersRef = useRef<FiltersHandleType>(null)
37
56
  const filters = useMemo(() => {
@@ -79,9 +98,23 @@ export function Styleguide({ className, elements, children }: StyleguideProps) {
79
98
  { label: 'Delete' },
80
99
  ], [])
81
100
 
82
- const Button = elements?.Button || ButtonNitro
101
+ const thead: TableColumn[] = useMemo(() => [
102
+ { value: 'freightType', label: 'Freight Type' },
103
+ { value: 'destination.code', label: 'Destination Code' },
104
+ { value: 'date', label: 'Date' },
105
+ { value: 'weight', label: 'Weight', align: 'center' },
106
+ { value: 'status', label: 'Status' },
107
+ { value: 'actions', label: 'Actions', disableSort: true, overflow: true, minWidth: 100, align: 'right' },
108
+ ], [])
109
+
110
+ const rows: QuoteExample[] = useMemo(() => [
111
+ { _id: '1', freightType: 'air', destination: { code: 'nz' }, date: new Date().getTime(), weight: 100, status: 'pending' },
112
+ { _id: '2', freightType: 'sea', destination: { code: 'nz' }, date: new Date().getTime(), weight: 200, status: 'approved' },
113
+ { _id: '3', freightType: 'road', destination: { code: 'au' }, date: new Date().getTime(), weight: 300, status: 'rejected' },
114
+ // normally you should filter the rows on the api using the query string
115
+ ].filter((row) => row.freightType.match(new RegExp(state.tableFilter, 'i'))), [state.tableFilter])
83
116
 
84
- function onCustomerInputChange (e: { target: { name: string, value: unknown } }) {
117
+ const onCustomerInputChange = (e: { target: { name: string, value: unknown } }) => {
85
118
  if (e.target.name == 'customer' && e.target.value == '0') {
86
119
  setCustomerSearch('')
87
120
  e.target.value = null // clear the select's selected value
@@ -90,10 +123,45 @@ export function Styleguide({ className, elements, children }: StyleguideProps) {
90
123
  onChange(setState, e)
91
124
  }
92
125
 
93
- function onCustomerSearch (search: string) {
126
+ const onCustomerSearch = (search: string) => {
94
127
  setCustomerSearch(search || '')
95
128
  }
96
129
 
130
+ const generateCheckboxActions = useCallback((selectedRowIds: string[]) => {
131
+ return <div class='flex items-center gap-x-2'>
132
+ <Button size='xs' color='dark' onClick={() => { console.log('set', selectedRowIds) }}>Set Status</Button>
133
+ <Button size='xs' color='dark' onClick={() => { console.log('remove', selectedRowIds) }}>Delete</Button>
134
+ </div>
135
+ }, [])
136
+
137
+ const generateTd = useCallback((col: TableColumn, row: QuoteExample, i: number) => {
138
+ switch (col.value) {
139
+ case 'freightType':
140
+ return <div>{ucFirst(row.freightType)}</div>
141
+ case 'destination.code':
142
+ return <div>{row.destination.code.toUpperCase()}</div>
143
+ case 'date':
144
+ return <div>{date(row.date, 'dd mmm, yyyy')}</div>
145
+ case 'weight':
146
+ return <div>{row.weight}</div>
147
+ case 'status':
148
+ return <div>{ucFirst(row.status)}</div>
149
+ case 'actions':
150
+ return (
151
+ <Dropdown
152
+ options={[{ label: 'Set Status' }, { label: 'Delete' }]}
153
+ dir={rows.slice(0, perPage).length - 3 < i ? 'top-right' : 'bottom-right'}
154
+ minWidth={100}
155
+ >
156
+ <Button color='clear' className='ring-0' size='sm' IconCenter={<EllipsisVerticalIcon size={18} strokeWidth={1.5} />} />
157
+ </Dropdown>
158
+ )
159
+ default:
160
+ console.error(`Error: unexpected thead value: ${col.value}`)
161
+ return null
162
+ }
163
+ }, [rows.length])
164
+
97
165
  // Example of updating state
98
166
  // useEffect(() => {
99
167
  // setTimeout(() => {
@@ -103,6 +171,24 @@ export function Styleguide({ className, elements, children }: StyleguideProps) {
103
171
 
104
172
  return (
105
173
  <div class={`text-left max-w-[1100px] ${className}`}>
174
+ <Modal show={showModal1} setShow={setShowModal1}>
175
+ <h3 class="h3">Edit Profile</h3>
176
+ <p class="mb-5">An example modal containing a basic form for editing profiles.</p>
177
+ <form class="mb-8 text-left">
178
+ <div>
179
+ <label for="firstName2">First Name</label>
180
+ <Field name="firstName2" state={state} onChange={(e) => onChange(setState, e)} />
181
+ </div>
182
+ <div>
183
+ <label for="email2">Email Address</label>
184
+ <Field name="email2" type="email" placeholder="Your email address..."/>
185
+ </div>
186
+ </form>
187
+ <div class="flex justify-end">
188
+ <Button color="primary" onClick={() => setShowModal1(false)}>Save</Button>
189
+ </div>
190
+ </Modal>
191
+
106
192
  <GithubLink filename={__filename} />
107
193
  <div class="mb-7">
108
194
  <h1 class="h1">{injectedConfig.isDemo ? 'Design System' : 'Style Guide'}</h1>
@@ -367,26 +453,8 @@ export function Styleguide({ className, elements, children }: StyleguideProps) {
367
453
  </div>
368
454
  </div>
369
455
 
370
- <Modal show={showModal1} setShow={setShowModal1}>
371
- <h3 class="h3">Edit Profile</h3>
372
- <p class="mb-5">An example modal containing a basic form for editing profiles.</p>
373
- <form class="mb-8 text-left">
374
- <div>
375
- <label for="firstName2">First Name</label>
376
- <Field name="firstName2" state={state} onChange={(e) => onChange(setState, e)} />
377
- </div>
378
- <div>
379
- <label for="email2">Email Address</label>
380
- <Field name="email2" type="email" placeholder="Your email address..."/>
381
- </div>
382
- </form>
383
- <div class="flex justify-end">
384
- <Button color="primary" onClick={() => setShowModal1(false)}>Save</Button>
385
- </div>
386
- </Modal>
387
-
388
456
  <h2 class="h3">File Inputs & Calendar</h2>
389
- <div class="grid grid-cols-3 gap-x-6 mb-4 last:mb-0">
457
+ <div class="grid grid-cols-3 gap-x-6 mb-4">
390
458
  <div>
391
459
  <label for="avatar">Avatar</label>
392
460
  <Drop class="is-small" name="avatar" state={state} onChange={(e) => onChange(setState, e)} awsUrl={injectedConfig.awsUrl} />
@@ -401,6 +469,44 @@ export function Styleguide({ className, elements, children }: StyleguideProps) {
401
469
  </div>
402
470
  </div>
403
471
 
472
+ <div class="flex justify-between items-start">
473
+ <h2 class="h3">Tables</h2>
474
+ <Field
475
+ name="tableFilter"
476
+ type="search"
477
+ state={state}
478
+ placeholder="Basic table filter..."
479
+ onChange={(e) => onChange(setState, e)}
480
+ className="!my-0 [&>input]:font-normal [&>input]:text-xs [&>input]:py-1.5" /////todo: need to allow twmerge here
481
+ />
482
+ </div>
483
+ <div class="grid mb-4 last:mb-0">
484
+ <Table
485
+ rows={rows.slice(0, perPage)}
486
+ columns={thead}
487
+ rowSideColor={(row) => ({ className: row?.status == 'pending' ? 'bg-yellow-400' : '', width: 5 })}
488
+ generateCheckboxActions={generateCheckboxActions}
489
+ generateTd={generateTd}
490
+ className="mb-6"
491
+ />
492
+ <Table
493
+ rows={rows.slice(0, 2)}
494
+ columns={thead}
495
+ rowLinesMax={1}
496
+ headerHeightMin={35}
497
+ rowSpacing={8}
498
+ rowHeightMin={52}
499
+ rowSideColor={(row) => ({ className: `rounded-l-xl ${statusColors(row?.status as string)}`, width: 10 })}
500
+ generateCheckboxActions={generateCheckboxActions}
501
+ generateTd={generateTd}
502
+ tableClassName="rounded-3px"
503
+ columnClassName="border-t-1 first:rounded-l-xl last:rounded-r-xl"
504
+ columnHeaderClassName="text-gray-500 text-2xs uppercase border-none"
505
+ checkboxClassName="rounded-[2px] shadow-none"
506
+ className="mb-5"
507
+ />
508
+ </div>
509
+
404
510
  {children}
405
511
  </div>
406
512
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitro-web",
3
- "version": "0.0.102",
3
+ "version": "0.0.104",
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 🚀",