nitro-web 0.0.123 → 0.0.125
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/components/partials/element/filters.tsx +14 -12
- package/components/partials/styleguide.tsx +3 -2
- package/package.json +1 -1
- package/types/util.d.ts +39 -11
- package/types/util.d.ts.map +1 -1
- package/util.js +64 -16
|
@@ -5,7 +5,7 @@ import { ListFilterIcon } from 'lucide-react'
|
|
|
5
5
|
|
|
6
6
|
type CommonProps = {
|
|
7
7
|
label?: string
|
|
8
|
-
|
|
8
|
+
width?: 'full' | 'half' | 'third' | 'quarter' | 'fifth'
|
|
9
9
|
rowClassName?: string
|
|
10
10
|
}
|
|
11
11
|
export type FilterType = (
|
|
@@ -109,7 +109,7 @@ export const Filters = forwardRef<FiltersHandleType, FiltersProps>(({
|
|
|
109
109
|
|
|
110
110
|
async function onInputChange(e: {target: {name: string, value: unknown}}) {
|
|
111
111
|
// console.log('onInputChange', e.target.name, e.target.value)
|
|
112
|
-
//
|
|
112
|
+
// the state is flattened for the query string, so here we se full paths as the key names e.g. 'job.location': '10')
|
|
113
113
|
setState((s) => ({ ...s, [e.target.name]: e.target.value as string }))
|
|
114
114
|
onAfterChange()
|
|
115
115
|
}
|
|
@@ -125,13 +125,13 @@ export const Filters = forwardRef<FiltersHandleType, FiltersProps>(({
|
|
|
125
125
|
navigate(locationRef.current.pathname + queryStr, { replace: true })
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
function getBasisWidth(
|
|
128
|
+
function getBasisWidth(width: 'full' | 'half' | 'third' | 'quarter' | 'fifth') {
|
|
129
129
|
// Need to splay out the classnames for tailwind to work
|
|
130
|
-
if (
|
|
131
|
-
else if (
|
|
132
|
-
else if (
|
|
133
|
-
else if (
|
|
134
|
-
else if (
|
|
130
|
+
if (width == 'full') return 'w-full'
|
|
131
|
+
else if (width == 'half') return 'shrink basis-[calc(50%-8px)]'
|
|
132
|
+
else if (width == 'third') return 'shrink basis-[calc(33.33%-8px)]'
|
|
133
|
+
else if (width == 'quarter') return 'shrink basis-[calc(25%-8px)]'
|
|
134
|
+
else if (width == 'fifth') return 'shrink basis-[calc(20%-8px)]'
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
return (
|
|
@@ -153,8 +153,8 @@ export const Filters = forwardRef<FiltersHandleType, FiltersProps>(({
|
|
|
153
153
|
</div> */}
|
|
154
154
|
<div class={twMerge(`flex flex-wrap gap-[16px] p-[16px] pb-6 ${dropdownFiltersClassName || ''}`)}>
|
|
155
155
|
{
|
|
156
|
-
filters?.map(({label,
|
|
157
|
-
<div key={i} class={twMerge(getBasisWidth(
|
|
156
|
+
filters?.map(({label, width='full', rowClassName, ...filter}, i) => (
|
|
157
|
+
<div key={i} class={twMerge(getBasisWidth(width), rowClassName || '')}>
|
|
158
158
|
<div class="flex justify-between">
|
|
159
159
|
<label for={filter.id || filter.name}>{label || camelCaseToTitle(filter.name)}</label>
|
|
160
160
|
<a href="#" class="label font-normal text-secondary underline" onClick={(e) => reset(e, filter)}>Reset</a>
|
|
@@ -164,7 +164,8 @@ export const Filters = forwardRef<FiltersHandleType, FiltersProps>(({
|
|
|
164
164
|
<Elements.Select
|
|
165
165
|
{...filter}
|
|
166
166
|
class="!mb-0"
|
|
167
|
-
|
|
167
|
+
// `filter.name` is a full path e.g. 'job.location', not just the key `location`
|
|
168
|
+
value={typeof state[filter.name] === 'undefined' ? '' : state[filter.name]}
|
|
168
169
|
onChange={onInputChange}
|
|
169
170
|
type={undefined}
|
|
170
171
|
/>
|
|
@@ -174,7 +175,8 @@ export const Filters = forwardRef<FiltersHandleType, FiltersProps>(({
|
|
|
174
175
|
<Elements.Field
|
|
175
176
|
{...filter}
|
|
176
177
|
class="!mb-0"
|
|
177
|
-
|
|
178
|
+
// `filter.name` is a full path e.g. 'job.location', not just the key `location`
|
|
179
|
+
value={typeof state[filter.name] === 'undefined' ? '' : state[filter.name] as string}
|
|
178
180
|
onChange={onInputChange}
|
|
179
181
|
/>
|
|
180
182
|
}
|
|
@@ -73,7 +73,7 @@ export function Styleguide({ className, elements, children, currencies }: Styleg
|
|
|
73
73
|
{
|
|
74
74
|
type: 'select',
|
|
75
75
|
name: 'status',
|
|
76
|
-
|
|
76
|
+
width: 'half',
|
|
77
77
|
options: [
|
|
78
78
|
{ label: 'Pending', value: 'pending' },
|
|
79
79
|
{ label: 'Approved', value: 'approved' },
|
|
@@ -85,7 +85,8 @@ export function Styleguide({ className, elements, children, currencies }: Styleg
|
|
|
85
85
|
name: 'color',
|
|
86
86
|
label: 'Half column',
|
|
87
87
|
placeholder: 'Select color...',
|
|
88
|
-
rowClassName: '
|
|
88
|
+
rowClassName: 'italic', // for custom styling
|
|
89
|
+
width: 'half',
|
|
89
90
|
},
|
|
90
91
|
]
|
|
91
92
|
return filters
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nitro-web",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.125",
|
|
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 🚀",
|
package/types/util.d.ts
CHANGED
|
@@ -528,36 +528,58 @@ export function pad(num?: number, padLeft?: number, fixedRight?: number): string
|
|
|
528
528
|
* Validates req.query "filters" against a config object, and returns a MongoDB-compatible query object.
|
|
529
529
|
* @param {{ [key: string]: string }} query - req.query
|
|
530
530
|
* E.g. {
|
|
531
|
-
* createdAt: '1749038400000,1749729600000',
|
|
532
531
|
* location: '10-RS',
|
|
532
|
+
* age: '33',
|
|
533
|
+
* isDeleted: 'false',
|
|
534
|
+
* search: 'John Doe',
|
|
535
|
+
* createdAt: '1749038400000,1749729600000',
|
|
533
536
|
* status: 'incomplete',
|
|
534
|
-
*
|
|
537
|
+
* bookingDate: '14'
|
|
538
|
+
* isActive: 'true',
|
|
539
|
+
* customer.0: '1234567890', // splayed array items
|
|
535
540
|
* }
|
|
536
|
-
* @param {{
|
|
541
|
+
* @param {{
|
|
542
|
+
* [key: string]: 'string'|'number'|'boolean'|'search'|'dateRange'|EnumArray|{ rule: 'ids', parseId: parseId }
|
|
543
|
+
* }} config - allowed filters and their rules
|
|
537
544
|
* E.g. {
|
|
538
|
-
* createdAt: 'dateRange',
|
|
539
545
|
* location: 'string',
|
|
540
|
-
*
|
|
541
|
-
*
|
|
546
|
+
* age: 'number',
|
|
547
|
+
* isDeleted: 'boolean',
|
|
548
|
+
* search: 'search',
|
|
549
|
+
* createdAt: 'dateRange',
|
|
550
|
+
* status: ['incomplete', 'complete'], // EnumArray
|
|
551
|
+
* bookingDate: [11, 14, 33], // EnumArray
|
|
552
|
+
* isActive: [true, false], // EnumArray
|
|
553
|
+
* customer: { rule: 'ids', ObjectId: ObjectIdConstructor },
|
|
542
554
|
* }
|
|
543
555
|
* @example returned object (using the examples above):
|
|
544
556
|
* E.g. {
|
|
545
|
-
* date: { $gte: 1749038400000, $lte: 1749729600000 },
|
|
546
557
|
* location: '10-RS',
|
|
558
|
+
* age: 33,
|
|
559
|
+
* isDeleted: false,
|
|
560
|
+
* search: { $search: 'John' },
|
|
561
|
+
* createdAt: { $gte: 1749038400000, $lte: 1749729600000 },
|
|
547
562
|
* status: 'incomplete',
|
|
548
|
-
*
|
|
563
|
+
* bookingDate: 14,
|
|
564
|
+
* isActive: true,
|
|
565
|
+
* customer: { $in: [new ObjectId('1234567890')] },
|
|
549
566
|
* }
|
|
550
567
|
*/
|
|
551
568
|
export function parseFilters(query: {
|
|
552
569
|
[key: string]: string;
|
|
553
570
|
}, config: {
|
|
554
|
-
[key: string]: "string" | "number" | "search" | "dateRange" |
|
|
571
|
+
[key: string]: "string" | "number" | "boolean" | "search" | "dateRange" | EnumArray | {
|
|
572
|
+
rule: "ids";
|
|
573
|
+
parseId: parseId;
|
|
574
|
+
};
|
|
555
575
|
}): {
|
|
556
|
-
[key: string]: string | number |
|
|
576
|
+
[key: string]: string | number | boolean | {
|
|
577
|
+
$search: string;
|
|
578
|
+
} | {
|
|
557
579
|
$gte: number;
|
|
558
580
|
$lte?: number;
|
|
559
581
|
} | {
|
|
560
|
-
$
|
|
582
|
+
$in: ObjectId[];
|
|
561
583
|
};
|
|
562
584
|
};
|
|
563
585
|
/**
|
|
@@ -838,4 +860,10 @@ export type AxiosRequestConfigWithRetry = AxiosRequestConfig & {
|
|
|
838
860
|
export type AxiosInstanceWithRetry = Omit<AxiosInstance, "get"> & {
|
|
839
861
|
get<T = any, R = AxiosResponse, D = any>(url: string, config?: AxiosRequestConfigWithRetry): Promise<R>;
|
|
840
862
|
};
|
|
863
|
+
export type ObjectId = object;
|
|
864
|
+
export type parseId = (value: string) => ObjectId;
|
|
865
|
+
/**
|
|
866
|
+
* - an array of strings, numbers or booleans
|
|
867
|
+
*/
|
|
868
|
+
export type EnumArray = (string | number | boolean)[];
|
|
841
869
|
//# sourceMappingURL=util.d.ts.map
|
package/types/util.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../util.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../util.js"],"names":[],"mappings":"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"}
|
package/util.js
CHANGED
|
@@ -22,6 +22,10 @@ import { twMerge as _twMerge } from 'tailwind-merge'
|
|
|
22
22
|
* }} AxiosInstanceWithRetry
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
/** @typedef {object} ObjectId */
|
|
26
|
+
/** @typedef {(value: string) => ObjectId} parseId */
|
|
27
|
+
/** @typedef {(string|number|boolean)[]} EnumArray - an array of strings, numbers or booleans */
|
|
28
|
+
|
|
25
29
|
/** @type {{[key: string]: {[key: string]: string|true}}} */
|
|
26
30
|
let queryObjectCache = {}
|
|
27
31
|
|
|
@@ -1232,31 +1236,51 @@ export function pad (num=0, padLeft=0, fixedRight) {
|
|
|
1232
1236
|
* Validates req.query "filters" against a config object, and returns a MongoDB-compatible query object.
|
|
1233
1237
|
* @param {{ [key: string]: string }} query - req.query
|
|
1234
1238
|
* E.g. {
|
|
1235
|
-
* createdAt: '1749038400000,1749729600000',
|
|
1236
1239
|
* location: '10-RS',
|
|
1240
|
+
* age: '33',
|
|
1241
|
+
* isDeleted: 'false',
|
|
1242
|
+
* search: 'John Doe',
|
|
1243
|
+
* createdAt: '1749038400000,1749729600000',
|
|
1237
1244
|
* status: 'incomplete',
|
|
1238
|
-
*
|
|
1245
|
+
* bookingDate: '14'
|
|
1246
|
+
* isActive: 'true',
|
|
1247
|
+
* customer.0: '1234567890', // splayed array items
|
|
1239
1248
|
* }
|
|
1240
|
-
* @param {{
|
|
1249
|
+
* @param {{
|
|
1250
|
+
* [key: string]: 'string'|'number'|'boolean'|'search'|'dateRange'|EnumArray|{ rule: 'ids', parseId: parseId }
|
|
1251
|
+
* }} config - allowed filters and their rules
|
|
1241
1252
|
* E.g. {
|
|
1242
|
-
* createdAt: 'dateRange',
|
|
1243
1253
|
* location: 'string',
|
|
1244
|
-
*
|
|
1245
|
-
*
|
|
1254
|
+
* age: 'number',
|
|
1255
|
+
* isDeleted: 'boolean',
|
|
1256
|
+
* search: 'search',
|
|
1257
|
+
* createdAt: 'dateRange',
|
|
1258
|
+
* status: ['incomplete', 'complete'], // EnumArray
|
|
1259
|
+
* bookingDate: [11, 14, 33], // EnumArray
|
|
1260
|
+
* isActive: [true, false], // EnumArray
|
|
1261
|
+
* customer: { rule: 'ids', ObjectId: ObjectIdConstructor },
|
|
1246
1262
|
* }
|
|
1247
1263
|
* @example returned object (using the examples above):
|
|
1248
1264
|
* E.g. {
|
|
1249
|
-
* date: { $gte: 1749038400000, $lte: 1749729600000 },
|
|
1250
1265
|
* location: '10-RS',
|
|
1266
|
+
* age: 33,
|
|
1267
|
+
* isDeleted: false,
|
|
1268
|
+
* search: { $search: 'John' },
|
|
1269
|
+
* createdAt: { $gte: 1749038400000, $lte: 1749729600000 },
|
|
1251
1270
|
* status: 'incomplete',
|
|
1252
|
-
*
|
|
1271
|
+
* bookingDate: 14,
|
|
1272
|
+
* isActive: true,
|
|
1273
|
+
* customer: { $in: [new ObjectId('1234567890')] },
|
|
1253
1274
|
* }
|
|
1254
1275
|
*/
|
|
1255
1276
|
export function parseFilters(query, config) {
|
|
1256
|
-
/**
|
|
1277
|
+
/**
|
|
1278
|
+
* Should match the example returned object above
|
|
1279
|
+
* @type {{
|
|
1280
|
+
* [key: string]: string|number|boolean|{ $search: string }|{ $gte: number; $lte?: number; }|{ $in: ObjectId[] } }} */
|
|
1257
1281
|
const mongoQuery = {}
|
|
1258
1282
|
|
|
1259
|
-
// Convert splayed array items into a unified array objects
|
|
1283
|
+
// Convert splayed array items into a unified array objects, e.g. 'customer.0' = '1' and 'customer.1' = '2' -> 'customer' = '1,2'
|
|
1260
1284
|
for (const key in query) {
|
|
1261
1285
|
if (key.match(/\.\d+$/)) {
|
|
1262
1286
|
const baseKey = key.replace(/\.\d+$/, '')
|
|
@@ -1278,18 +1302,42 @@ export function parseFilters(query, config) {
|
|
|
1278
1302
|
mongoQuery[key] = val
|
|
1279
1303
|
|
|
1280
1304
|
} else if (rule === 'number') {
|
|
1281
|
-
|
|
1282
|
-
|
|
1305
|
+
const num = parseFloat(val)
|
|
1306
|
+
if (isNaN(num)) throw new Error(`The "${key}" filter should be a number, but received "${val}".`)
|
|
1307
|
+
mongoQuery[key] = num
|
|
1308
|
+
|
|
1309
|
+
} else if (rule === 'boolean') {
|
|
1310
|
+
const bool = val === 'true' ? true : val === 'false' ? false : undefined
|
|
1311
|
+
if (bool === undefined) throw new Error(`The "${key}" filter should be a boolean, but received "${val}".`)
|
|
1312
|
+
mongoQuery[key] = bool
|
|
1283
1313
|
|
|
1284
1314
|
} else if (rule === 'search') {
|
|
1285
1315
|
if (typeof val !== 'string') throw new Error(`The "${key}" filter has an invalid value "${val}". Expected a string.`)
|
|
1286
1316
|
mongoQuery['$text'] = { $search: '"' + val + '"' }
|
|
1287
1317
|
|
|
1318
|
+
// Enums
|
|
1288
1319
|
} else if (Array.isArray(rule)) {
|
|
1289
|
-
|
|
1290
|
-
|
|
1320
|
+
// Detetect the entire array's type from the first item
|
|
1321
|
+
const type = typeof rule[0]
|
|
1322
|
+
if (!['string', 'number', 'boolean'].includes(type)) {
|
|
1323
|
+
throw new Error(`The rule for "${key}" should only contain strings, numbers or booleans, but received "${type}".`)
|
|
1291
1324
|
}
|
|
1292
|
-
|
|
1325
|
+
// Parse the value to the correct type and compare it to the rule item
|
|
1326
|
+
for (const ruleItem of rule) {
|
|
1327
|
+
let valParsed = /** @type {string|number|boolean|undefined} */(val)
|
|
1328
|
+
if (type === 'number') valParsed = parseFloat(val)
|
|
1329
|
+
else if (type === 'boolean') valParsed = val === 'true' ? true : val === 'false' ? false : undefined
|
|
1330
|
+
if (valParsed === ruleItem) mongoQuery[key] = valParsed
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// Ids
|
|
1334
|
+
} else if (typeof rule === 'object' && 'rule' in rule && rule.rule === 'ids') {
|
|
1335
|
+
const ids = val.split(',').map(id => {
|
|
1336
|
+
if (!isHex24(id)) throw new Error(`Invalid id "${id}" passed to the "${key}" filter.`)
|
|
1337
|
+
else return rule.parseId(id)
|
|
1338
|
+
})
|
|
1339
|
+
if (!ids.length) throw new Error(`Please pass at least one id to the "${key}" filter.`)
|
|
1340
|
+
mongoQuery[key] = { $in: ids }
|
|
1293
1341
|
|
|
1294
1342
|
} else if (rule === 'dateRange') {
|
|
1295
1343
|
const [start, end] = val.split(',').map(Number)
|
|
@@ -1423,7 +1471,7 @@ export function queryString (obj, _path='', _output) {
|
|
|
1423
1471
|
|
|
1424
1472
|
for (let key in obj) {
|
|
1425
1473
|
if (obj.hasOwnProperty(key)) {
|
|
1426
|
-
if (typeof obj[key] == 'undefined' ||
|
|
1474
|
+
if (typeof obj[key] == 'undefined' || obj[key] === '') continue
|
|
1427
1475
|
else if (typeof obj[key] == 'object') queryString(/** @type {{[key: string]: unknown}} */(obj[key]), _path + key + '.', output)
|
|
1428
1476
|
else output[_path + key] = obj[key] + ''
|
|
1429
1477
|
}
|