hazo_ui 1.0.3 → 2.0.0
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/README.md +229 -31
- package/dist/index.cjs +336 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +336 -2
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -10,17 +10,25 @@ npm install hazo_ui
|
|
|
10
10
|
|
|
11
11
|
## Components
|
|
12
12
|
|
|
13
|
-
###
|
|
13
|
+
### Component Overview
|
|
14
|
+
|
|
15
|
+
- **[MultiFilterDialog](#multifilterdialog)** - A powerful dialog component for multi-field filtering with support for text, number, combobox, boolean, and date fields. Includes operator support, validation, and visual feedback.
|
|
16
|
+
|
|
17
|
+
- **[MultiSortDialog](#multisortdialog)** - A flexible dialog component for multi-field sorting with drag-and-drop reordering. Allows users to set sort priority and direction (ascending/descending) for multiple fields.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## MultiFilterDialog
|
|
14
22
|
|
|
15
23
|
A powerful, flexible dialog component for multi-field filtering with support for various input types. Perfect for table headers, grid views, or any interface where users need to apply multiple filters simultaneously.
|
|
16
24
|
|
|
17
|
-

|
|
18
26
|
|
|
19
|
-

|
|
20
28
|
|
|
21
|
-

|
|
22
30
|
|
|
23
|
-

|
|
24
32
|
|
|
25
33
|
#### Features
|
|
26
34
|
|
|
@@ -29,6 +37,7 @@ A powerful, flexible dialog component for multi-field filtering with support for
|
|
|
29
37
|
- **Dynamic Field Addition**: Users can add and remove filter fields dynamically
|
|
30
38
|
- **Field Validation**: Built-in validation for text length, number ranges, and decimal precision
|
|
31
39
|
- **Visual Feedback**: Tooltip shows active filters when hovering over the filter button
|
|
40
|
+
- **Clear All Button**: Quickly clear all filters at once
|
|
32
41
|
- **Responsive Design**: Works seamlessly on mobile and desktop devices
|
|
33
42
|
- **TypeScript Support**: Fully typed with TypeScript interfaces
|
|
34
43
|
- **Accessible**: Built with accessibility in mind using Radix UI primitives
|
|
@@ -226,40 +235,218 @@ When users apply filters, the `onFilterChange` callback receives an array of `Fi
|
|
|
226
235
|
]
|
|
227
236
|
```
|
|
228
237
|
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## MultiSortDialog
|
|
241
|
+
|
|
242
|
+
A powerful dialog component for multi-field sorting with drag-and-drop reordering. Allows users to select multiple fields for sorting, reorder them by priority, and set ascending/descending direction for each field.
|
|
243
|
+
|
|
244
|
+

|
|
245
|
+
|
|
246
|
+

|
|
247
|
+
|
|
248
|
+

|
|
249
|
+
|
|
250
|
+

|
|
251
|
+
|
|
252
|
+
#### Features
|
|
253
|
+
|
|
254
|
+
- **Drag-and-Drop Reordering**: Intuitively reorder sort fields by dragging them
|
|
255
|
+
- **Multiple Sort Fields**: Add multiple fields to sort by with priority ordering
|
|
256
|
+
- **Direction Toggle**: Switch between ascending and descending for each field
|
|
257
|
+
- **Visual Feedback**: Drag handle with grip icon, opacity changes during drag
|
|
258
|
+
- **Clear All Button**: Quickly clear all sort fields at once
|
|
259
|
+
- **Tooltip Display**: Shows active sort configuration when hovering over the sort button
|
|
260
|
+
- **Keyboard Accessible**: Full keyboard navigation support for drag and drop
|
|
261
|
+
- **Responsive Design**: Works seamlessly on mobile and desktop devices
|
|
262
|
+
- **TypeScript Support**: Fully typed with TypeScript interfaces
|
|
263
|
+
- **Accessible**: Built with accessibility in mind using Radix UI primitives
|
|
264
|
+
|
|
265
|
+
#### How It Works
|
|
266
|
+
|
|
267
|
+
1. **Adding Sort Fields**: Click the "Add field" button to select from available fields
|
|
268
|
+
2. **Reordering**: Drag fields using the grip icon (⋮⋮) to change their priority
|
|
269
|
+
3. **Changing Direction**: Toggle the switch next to each field to change between ascending (A→Z, 0→9) and descending (Z→A, 9→0)
|
|
270
|
+
4. **Removing Fields**: Click the trash icon to remove a field from sorting
|
|
271
|
+
5. **Applying Sorts**: Click "Apply" to save the sort configuration
|
|
272
|
+
6. **Clearing All**: Click "Clear All" to remove all sort fields at once
|
|
273
|
+
|
|
274
|
+
The component returns an array of sort configurations in priority order, where the first item is the primary sort field, second is secondary, and so on.
|
|
275
|
+
|
|
276
|
+
#### Usage
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
import { MultiSortDialog, type SortField, type SortConfig } from 'hazo_ui';
|
|
280
|
+
import { useState } from 'react';
|
|
281
|
+
|
|
282
|
+
function DataTable() {
|
|
283
|
+
const [sorts, setSorts] = useState<SortConfig[]>([]);
|
|
284
|
+
|
|
285
|
+
// Define available sort fields
|
|
286
|
+
const availableFields: SortField[] = [
|
|
287
|
+
{
|
|
288
|
+
value: "name",
|
|
289
|
+
label: "Name",
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
value: "age",
|
|
293
|
+
label: "Age",
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
value: "price",
|
|
297
|
+
label: "Price",
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
value: "status",
|
|
301
|
+
label: "Status",
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
value: "created_date",
|
|
305
|
+
label: "Created Date",
|
|
306
|
+
},
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
// Handle sort changes
|
|
310
|
+
const handleSortChange = (sortConfig: SortConfig[]) => {
|
|
311
|
+
setSorts(sortConfig);
|
|
312
|
+
// Apply sorts to your data
|
|
313
|
+
console.log('Applied sorts:', sortConfig);
|
|
314
|
+
// Sort your data based on the configuration
|
|
315
|
+
// First item is primary sort, second is secondary, etc.
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<div>
|
|
320
|
+
<MultiSortDialog
|
|
321
|
+
availableFields={availableFields}
|
|
322
|
+
onSortChange={handleSortChange}
|
|
323
|
+
initialSortFields={sorts}
|
|
324
|
+
/>
|
|
325
|
+
{/* Your table/grid component */}
|
|
326
|
+
</div>
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
#### Example Input
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
// Available sort fields configuration
|
|
335
|
+
const availableFields: SortField[] = [
|
|
336
|
+
{
|
|
337
|
+
value: "name",
|
|
338
|
+
label: "Name",
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
value: "price",
|
|
342
|
+
label: "Price",
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
value: "created_date",
|
|
346
|
+
label: "Created Date",
|
|
347
|
+
},
|
|
348
|
+
];
|
|
349
|
+
|
|
350
|
+
// Initial sort fields (optional)
|
|
351
|
+
const initialSortFields: SortConfig[] = [
|
|
352
|
+
{
|
|
353
|
+
field: "name",
|
|
354
|
+
direction: "asc",
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
field: "price",
|
|
358
|
+
direction: "desc",
|
|
359
|
+
},
|
|
360
|
+
];
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
#### Expected Output
|
|
364
|
+
|
|
365
|
+
When users apply sorts, the `onSortChange` callback receives an array of `SortConfig` objects in priority order:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
// Example output when user applies sorts:
|
|
369
|
+
[
|
|
370
|
+
{
|
|
371
|
+
field: "name",
|
|
372
|
+
direction: "asc" // Primary sort: Name ascending
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
field: "price",
|
|
376
|
+
direction: "desc" // Secondary sort: Price descending
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
field: "created_date",
|
|
380
|
+
direction: "desc" // Tertiary sort: Created Date descending
|
|
381
|
+
}
|
|
382
|
+
]
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Important**: The order of the array matters! The first item is the primary sort field, the second is secondary, and so on. This allows for multi-level sorting (e.g., sort by name first, then by price for items with the same name).
|
|
386
|
+
|
|
229
387
|
#### TypeScript Interfaces
|
|
230
388
|
|
|
231
389
|
```typescript
|
|
232
|
-
interface
|
|
233
|
-
value: string;
|
|
234
|
-
label: string;
|
|
235
|
-
type: 'text' | 'number' | 'combobox' | 'boolean' | 'date';
|
|
236
|
-
textConfig?: {
|
|
237
|
-
minLength?: number;
|
|
238
|
-
maxLength?: number;
|
|
239
|
-
};
|
|
240
|
-
numberConfig?: {
|
|
241
|
-
min?: number;
|
|
242
|
-
max?: number;
|
|
243
|
-
allowDecimal?: boolean;
|
|
244
|
-
decimalLength?: number;
|
|
245
|
-
};
|
|
246
|
-
comboboxOptions?: Array<{ label: string; value: string }>;
|
|
247
|
-
booleanLabels?: {
|
|
248
|
-
trueLabel?: string;
|
|
249
|
-
falseLabel?: string;
|
|
250
|
-
};
|
|
390
|
+
interface SortField {
|
|
391
|
+
value: string; // Unique identifier for the field
|
|
392
|
+
label: string; // Display label
|
|
251
393
|
}
|
|
252
394
|
|
|
253
|
-
interface
|
|
254
|
-
field: string;
|
|
255
|
-
|
|
256
|
-
value: any; // Filter value (string, number, boolean, or Date)
|
|
395
|
+
interface SortConfig {
|
|
396
|
+
field: string; // Field identifier
|
|
397
|
+
direction: 'asc' | 'desc'; // Sort direction: 'asc' for ascending, 'desc' for descending
|
|
257
398
|
}
|
|
258
399
|
```
|
|
259
400
|
|
|
260
|
-
####
|
|
401
|
+
#### Implementing the Sort Logic
|
|
402
|
+
|
|
403
|
+
Here's an example of how to apply the sort configuration to your data:
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
function applySorts(data: any[], sortConfigs: SortConfig[]): any[] {
|
|
407
|
+
if (sortConfigs.length === 0) return data;
|
|
408
|
+
|
|
409
|
+
return [...data].sort((a, b) => {
|
|
410
|
+
for (const sortConfig of sortConfigs) {
|
|
411
|
+
const aValue = a[sortConfig.field];
|
|
412
|
+
const bValue = b[sortConfig.field];
|
|
413
|
+
|
|
414
|
+
let comparison = 0;
|
|
415
|
+
|
|
416
|
+
// Handle different value types
|
|
417
|
+
if (typeof aValue === 'string' && typeof bValue === 'string') {
|
|
418
|
+
comparison = aValue.localeCompare(bValue);
|
|
419
|
+
} else if (aValue instanceof Date && bValue instanceof Date) {
|
|
420
|
+
comparison = aValue.getTime() - bValue.getTime();
|
|
421
|
+
} else {
|
|
422
|
+
comparison = (aValue ?? 0) - (bValue ?? 0);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Apply direction
|
|
426
|
+
if (sortConfig.direction === 'desc') {
|
|
427
|
+
comparison = -comparison;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// If values are different, return the comparison
|
|
431
|
+
// Otherwise, continue to next sort field
|
|
432
|
+
if (comparison !== 0) {
|
|
433
|
+
return comparison;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return 0; // All sort fields are equal
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Usage
|
|
442
|
+
const sortedData = applySorts(originalData, sorts);
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## Styling
|
|
261
448
|
|
|
262
|
-
|
|
449
|
+
Both components use Tailwind CSS and follow shadcn/ui design patterns. Make sure your project has Tailwind CSS configured with the following CSS variables:
|
|
263
450
|
|
|
264
451
|
```css
|
|
265
452
|
:root {
|
|
@@ -267,7 +454,18 @@ The component uses Tailwind CSS and follows shadcn/ui design patterns. Make sure
|
|
|
267
454
|
--foreground: 222.2 84% 4.9%;
|
|
268
455
|
--primary: 222.2 47.4% 11.2%;
|
|
269
456
|
--primary-foreground: 210 40% 98%;
|
|
270
|
-
|
|
457
|
+
--secondary: 210 40% 96.1%;
|
|
458
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
459
|
+
--muted: 210 40% 96.1%;
|
|
460
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
461
|
+
--accent: 210 40% 96.1%;
|
|
462
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
463
|
+
--destructive: 0 84.2% 60.2%;
|
|
464
|
+
--destructive-foreground: 210 40% 98%;
|
|
465
|
+
--border: 214.3 31.8% 91.4%;
|
|
466
|
+
--input: 214.3 31.8% 91.4%;
|
|
467
|
+
--ring: 222.2 84% 4.9%;
|
|
468
|
+
--radius: 0.5rem;
|
|
271
469
|
}
|
|
272
470
|
```
|
|
273
471
|
|
package/dist/index.cjs
CHANGED
|
@@ -13,6 +13,10 @@ var SelectPrimitive = require('@radix-ui/react-select');
|
|
|
13
13
|
var TooltipPrimitive = require('@radix-ui/react-tooltip');
|
|
14
14
|
var reactDayPicker = require('react-day-picker');
|
|
15
15
|
var dateFns = require('date-fns');
|
|
16
|
+
var SwitchPrimitives = require('@radix-ui/react-switch');
|
|
17
|
+
var core = require('@dnd-kit/core');
|
|
18
|
+
var sortable = require('@dnd-kit/sortable');
|
|
19
|
+
var utilities = require('@dnd-kit/utilities');
|
|
16
20
|
|
|
17
21
|
function _interopNamespace(e) {
|
|
18
22
|
if (e && e.__esModule) return e;
|
|
@@ -37,6 +41,7 @@ var DialogPrimitive__namespace = /*#__PURE__*/_interopNamespace(DialogPrimitive)
|
|
|
37
41
|
var PopoverPrimitive__namespace = /*#__PURE__*/_interopNamespace(PopoverPrimitive);
|
|
38
42
|
var SelectPrimitive__namespace = /*#__PURE__*/_interopNamespace(SelectPrimitive);
|
|
39
43
|
var TooltipPrimitive__namespace = /*#__PURE__*/_interopNamespace(TooltipPrimitive);
|
|
44
|
+
var SwitchPrimitives__namespace = /*#__PURE__*/_interopNamespace(SwitchPrimitives);
|
|
40
45
|
|
|
41
46
|
var ExampleComponent = ({ children, className = "" }) => {
|
|
42
47
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `cls_example_component ${className}`, children });
|
|
@@ -695,6 +700,9 @@ function MultiFilterDialog({
|
|
|
695
700
|
setFilterFields(initialFilters);
|
|
696
701
|
setIsOpen(false);
|
|
697
702
|
};
|
|
703
|
+
const handleClearAll = () => {
|
|
704
|
+
setFilterFields([]);
|
|
705
|
+
};
|
|
698
706
|
const availableFieldsToAdd = availableFields.filter(
|
|
699
707
|
(af) => !filterFields.some((ff) => ff.field === af.value)
|
|
700
708
|
);
|
|
@@ -821,6 +829,333 @@ function MultiFilterDialog({
|
|
|
821
829
|
}) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_empty_filter_fields text-center py-8 text-sm text-muted-foreground", children: 'No filter fields added. Click "Add field" to add filtering criteria.' })
|
|
822
830
|
] }),
|
|
823
831
|
/* @__PURE__ */ jsxRuntime.jsxs(DialogFooter, { children: [
|
|
832
|
+
filterFields.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
833
|
+
Button,
|
|
834
|
+
{
|
|
835
|
+
variant: "outline",
|
|
836
|
+
onClick: handleClearAll,
|
|
837
|
+
className: "cls_clear_all_btn",
|
|
838
|
+
children: [
|
|
839
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "cls_clear_all_icon h-4 w-4 mr-2" }),
|
|
840
|
+
"Clear All"
|
|
841
|
+
]
|
|
842
|
+
}
|
|
843
|
+
),
|
|
844
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
845
|
+
Button,
|
|
846
|
+
{
|
|
847
|
+
onClick: handleApply,
|
|
848
|
+
className: "cls_apply_btn",
|
|
849
|
+
children: "Apply"
|
|
850
|
+
}
|
|
851
|
+
),
|
|
852
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
853
|
+
Button,
|
|
854
|
+
{
|
|
855
|
+
variant: "outline",
|
|
856
|
+
onClick: handleCancel,
|
|
857
|
+
className: "cls_cancel_btn",
|
|
858
|
+
children: "Cancel"
|
|
859
|
+
}
|
|
860
|
+
)
|
|
861
|
+
] })
|
|
862
|
+
] })
|
|
863
|
+
] });
|
|
864
|
+
}
|
|
865
|
+
var Switch = React6__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
866
|
+
SwitchPrimitives__namespace.Root,
|
|
867
|
+
{
|
|
868
|
+
className: cn(
|
|
869
|
+
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
|
870
|
+
className
|
|
871
|
+
),
|
|
872
|
+
...props,
|
|
873
|
+
ref,
|
|
874
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
875
|
+
SwitchPrimitives__namespace.Thumb,
|
|
876
|
+
{
|
|
877
|
+
className: cn(
|
|
878
|
+
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
|
|
879
|
+
)
|
|
880
|
+
}
|
|
881
|
+
)
|
|
882
|
+
}
|
|
883
|
+
));
|
|
884
|
+
Switch.displayName = SwitchPrimitives__namespace.Root.displayName;
|
|
885
|
+
var Label2 = React6__namespace.forwardRef(
|
|
886
|
+
({ className, ...props }, ref) => {
|
|
887
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
888
|
+
"label",
|
|
889
|
+
{
|
|
890
|
+
ref,
|
|
891
|
+
className: cn(
|
|
892
|
+
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
|
893
|
+
className
|
|
894
|
+
),
|
|
895
|
+
...props
|
|
896
|
+
}
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
);
|
|
900
|
+
Label2.displayName = "Label";
|
|
901
|
+
function SortableSortFieldItem({
|
|
902
|
+
sortConfig,
|
|
903
|
+
fieldLabel,
|
|
904
|
+
onDirectionChange,
|
|
905
|
+
onDelete
|
|
906
|
+
}) {
|
|
907
|
+
const {
|
|
908
|
+
attributes,
|
|
909
|
+
listeners,
|
|
910
|
+
setNodeRef,
|
|
911
|
+
transform,
|
|
912
|
+
transition,
|
|
913
|
+
isDragging
|
|
914
|
+
} = sortable.useSortable({ id: sortConfig.field });
|
|
915
|
+
const style = {
|
|
916
|
+
transform: utilities.CSS.Transform.toString(transform),
|
|
917
|
+
transition,
|
|
918
|
+
opacity: isDragging ? 0.5 : 1
|
|
919
|
+
};
|
|
920
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
921
|
+
"div",
|
|
922
|
+
{
|
|
923
|
+
ref: setNodeRef,
|
|
924
|
+
style,
|
|
925
|
+
className: "cls_sortable_sort_field_item flex items-center gap-3 p-3 border rounded-md bg-card",
|
|
926
|
+
children: [
|
|
927
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
928
|
+
"div",
|
|
929
|
+
{
|
|
930
|
+
...attributes,
|
|
931
|
+
...listeners,
|
|
932
|
+
className: "cls_drag_handle cursor-grab active:cursor-grabbing text-muted-foreground hover:text-foreground",
|
|
933
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.GripVertical, { className: "cls_drag_icon h-4 w-4" })
|
|
934
|
+
}
|
|
935
|
+
),
|
|
936
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_field_label flex-1 text-sm font-medium", children: fieldLabel }),
|
|
937
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cls_direction_control flex items-center gap-2", children: [
|
|
938
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label2, { htmlFor: `switch-${sortConfig.field}`, className: "cls_direction_label text-xs text-muted-foreground", children: sortConfig.direction === "asc" ? "Ascending" : "Descending" }),
|
|
939
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
940
|
+
Switch,
|
|
941
|
+
{
|
|
942
|
+
id: `switch-${sortConfig.field}`,
|
|
943
|
+
checked: sortConfig.direction === "desc",
|
|
944
|
+
onCheckedChange: (checked) => onDirectionChange(checked ? "desc" : "asc"),
|
|
945
|
+
"aria-label": `Toggle sort direction for ${fieldLabel}`
|
|
946
|
+
}
|
|
947
|
+
)
|
|
948
|
+
] }),
|
|
949
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
950
|
+
Button,
|
|
951
|
+
{
|
|
952
|
+
variant: "ghost",
|
|
953
|
+
size: "sm",
|
|
954
|
+
onClick: onDelete,
|
|
955
|
+
className: "cls_delete_btn h-8 w-8 p-0 text-destructive hover:text-destructive",
|
|
956
|
+
"aria-label": `Remove ${fieldLabel} from sort`,
|
|
957
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "cls_delete_icon h-4 w-4" })
|
|
958
|
+
}
|
|
959
|
+
)
|
|
960
|
+
]
|
|
961
|
+
}
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
function MultiSortDialog({
|
|
965
|
+
availableFields,
|
|
966
|
+
onSortChange,
|
|
967
|
+
initialSortFields = []
|
|
968
|
+
}) {
|
|
969
|
+
const [isOpen, setIsOpen] = React6.useState(false);
|
|
970
|
+
const [sortFields, setSortFields] = React6.useState(initialSortFields);
|
|
971
|
+
const [isComboboxOpen, setIsComboboxOpen] = React6.useState(false);
|
|
972
|
+
const sensors = core.useSensors(
|
|
973
|
+
core.useSensor(core.PointerSensor),
|
|
974
|
+
core.useSensor(core.KeyboardSensor, {
|
|
975
|
+
coordinateGetter: sortable.sortableKeyboardCoordinates
|
|
976
|
+
})
|
|
977
|
+
);
|
|
978
|
+
React6.useEffect(() => {
|
|
979
|
+
if (isOpen) {
|
|
980
|
+
setSortFields(initialSortFields);
|
|
981
|
+
}
|
|
982
|
+
}, [isOpen, initialSortFields]);
|
|
983
|
+
const handleAddField = (fieldValue) => {
|
|
984
|
+
if (sortFields.some((sf) => sf.field === fieldValue)) {
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
const newField = {
|
|
988
|
+
field: fieldValue,
|
|
989
|
+
direction: "asc"
|
|
990
|
+
};
|
|
991
|
+
setSortFields([...sortFields, newField]);
|
|
992
|
+
setIsComboboxOpen(false);
|
|
993
|
+
};
|
|
994
|
+
const handleDeleteField = (fieldValue) => {
|
|
995
|
+
setSortFields(sortFields.filter((sf) => sf.field !== fieldValue));
|
|
996
|
+
};
|
|
997
|
+
const handleDirectionChange = (fieldValue, direction) => {
|
|
998
|
+
setSortFields(
|
|
999
|
+
sortFields.map(
|
|
1000
|
+
(sf) => sf.field === fieldValue ? { ...sf, direction } : sf
|
|
1001
|
+
)
|
|
1002
|
+
);
|
|
1003
|
+
};
|
|
1004
|
+
const handleDragEnd = (event) => {
|
|
1005
|
+
const { active, over } = event;
|
|
1006
|
+
if (!over || active.id === over.id) {
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
const oldIndex = sortFields.findIndex((sf) => sf.field === active.id);
|
|
1010
|
+
const newIndex = sortFields.findIndex((sf) => sf.field === over.id);
|
|
1011
|
+
if (oldIndex !== -1 && newIndex !== -1) {
|
|
1012
|
+
setSortFields(sortable.arrayMove(sortFields, oldIndex, newIndex));
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
const handleApply = () => {
|
|
1016
|
+
onSortChange([...sortFields]);
|
|
1017
|
+
setIsOpen(false);
|
|
1018
|
+
};
|
|
1019
|
+
const handleCancel = () => {
|
|
1020
|
+
setSortFields(initialSortFields);
|
|
1021
|
+
setIsOpen(false);
|
|
1022
|
+
};
|
|
1023
|
+
const handleClearAll = () => {
|
|
1024
|
+
setSortFields([]);
|
|
1025
|
+
};
|
|
1026
|
+
const availableFieldsToAdd = availableFields.filter(
|
|
1027
|
+
(af) => !sortFields.some((sf) => sf.field === af.value)
|
|
1028
|
+
);
|
|
1029
|
+
const getFieldLabel = (fieldValue) => {
|
|
1030
|
+
return availableFields.find((af) => af.value === fieldValue)?.label || fieldValue;
|
|
1031
|
+
};
|
|
1032
|
+
const hasActiveSorts = initialSortFields.length > 0;
|
|
1033
|
+
const tooltipContent = hasActiveSorts ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cls_sort_tooltip_content space-y-1", children: [
|
|
1034
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_tooltip_title text-xs font-semibold mb-1", children: "Active Sorts:" }),
|
|
1035
|
+
initialSortFields.map((sortConfig, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cls_tooltip_item text-xs", children: [
|
|
1036
|
+
index + 1,
|
|
1037
|
+
". ",
|
|
1038
|
+
getFieldLabel(sortConfig.field),
|
|
1039
|
+
" (",
|
|
1040
|
+
sortConfig.direction === "asc" ? "Asc" : "Desc",
|
|
1041
|
+
")"
|
|
1042
|
+
] }, sortConfig.field))
|
|
1043
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_sort_tooltip_content text-xs", children: "No active sorts" });
|
|
1044
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Dialog, { open: isOpen, onOpenChange: setIsOpen, children: [
|
|
1045
|
+
/* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
|
|
1046
|
+
/* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(DialogTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1047
|
+
Button,
|
|
1048
|
+
{
|
|
1049
|
+
variant: "outline",
|
|
1050
|
+
size: "sm",
|
|
1051
|
+
className: "cls_sort_button",
|
|
1052
|
+
"aria-label": "Open sort dialog",
|
|
1053
|
+
children: [
|
|
1054
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1055
|
+
lucideReact.ArrowUpDown,
|
|
1056
|
+
{
|
|
1057
|
+
className: cn(
|
|
1058
|
+
"cls_sort_icon h-4 w-4 mr-2",
|
|
1059
|
+
hasActiveSorts && "text-blue-600"
|
|
1060
|
+
)
|
|
1061
|
+
}
|
|
1062
|
+
),
|
|
1063
|
+
"Sort"
|
|
1064
|
+
]
|
|
1065
|
+
}
|
|
1066
|
+
) }) }),
|
|
1067
|
+
/* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: tooltipContent })
|
|
1068
|
+
] }) }),
|
|
1069
|
+
/* @__PURE__ */ jsxRuntime.jsxs(DialogContent, { className: "cls_sort_dialog_content max-w-lg", children: [
|
|
1070
|
+
/* @__PURE__ */ jsxRuntime.jsxs(DialogHeader, { children: [
|
|
1071
|
+
/* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: "Sort Images" }),
|
|
1072
|
+
/* @__PURE__ */ jsxRuntime.jsx(DialogDescription, { children: "Add multiple fields to sort by and reorder them. Use the switch to toggle between ascending and descending." })
|
|
1073
|
+
] }),
|
|
1074
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cls_sort_dialog_body space-y-4 py-4", children: [
|
|
1075
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_add_field_section", children: /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open: isComboboxOpen, onOpenChange: setIsComboboxOpen, children: [
|
|
1076
|
+
/* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1077
|
+
Button,
|
|
1078
|
+
{
|
|
1079
|
+
variant: "outline",
|
|
1080
|
+
role: "combobox",
|
|
1081
|
+
"aria-expanded": isComboboxOpen,
|
|
1082
|
+
className: "cls_add_field_combobox w-full justify-between",
|
|
1083
|
+
children: [
|
|
1084
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cls_combobox_content flex items-center", children: [
|
|
1085
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "cls_plus_icon h-4 w-4 mr-2" }),
|
|
1086
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Add field" })
|
|
1087
|
+
] }),
|
|
1088
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsUpDown, { className: "cls_chevron_icon ml-2 h-4 w-4 shrink-0 opacity-50" })
|
|
1089
|
+
]
|
|
1090
|
+
}
|
|
1091
|
+
) }),
|
|
1092
|
+
/* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { className: "cls_combobox_popover w-full p-0", children: /* @__PURE__ */ jsxRuntime.jsxs(Command, { children: [
|
|
1093
|
+
/* @__PURE__ */ jsxRuntime.jsx(CommandInput, { placeholder: "Search fields...", className: "cls_command_input" }),
|
|
1094
|
+
/* @__PURE__ */ jsxRuntime.jsxs(CommandList, { children: [
|
|
1095
|
+
/* @__PURE__ */ jsxRuntime.jsx(CommandEmpty, { children: "No fields found." }),
|
|
1096
|
+
/* @__PURE__ */ jsxRuntime.jsx(CommandGroup, { children: availableFieldsToAdd.map((field) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1097
|
+
CommandItem,
|
|
1098
|
+
{
|
|
1099
|
+
value: field.value,
|
|
1100
|
+
onSelect: () => handleAddField(field.value),
|
|
1101
|
+
className: "cls_command_item",
|
|
1102
|
+
children: [
|
|
1103
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1104
|
+
lucideReact.Check,
|
|
1105
|
+
{
|
|
1106
|
+
className: cn(
|
|
1107
|
+
"cls_check_icon mr-2 h-4 w-4",
|
|
1108
|
+
"opacity-0"
|
|
1109
|
+
)
|
|
1110
|
+
}
|
|
1111
|
+
),
|
|
1112
|
+
field.label
|
|
1113
|
+
]
|
|
1114
|
+
},
|
|
1115
|
+
field.value
|
|
1116
|
+
)) })
|
|
1117
|
+
] })
|
|
1118
|
+
] }) })
|
|
1119
|
+
] }) }),
|
|
1120
|
+
sortFields.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_sort_fields_list space-y-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1121
|
+
core.DndContext,
|
|
1122
|
+
{
|
|
1123
|
+
sensors,
|
|
1124
|
+
collisionDetection: core.closestCenter,
|
|
1125
|
+
onDragEnd: handleDragEnd,
|
|
1126
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1127
|
+
sortable.SortableContext,
|
|
1128
|
+
{
|
|
1129
|
+
items: sortFields.map((sf) => sf.field),
|
|
1130
|
+
strategy: sortable.verticalListSortingStrategy,
|
|
1131
|
+
children: sortFields.map((sortConfig) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1132
|
+
SortableSortFieldItem,
|
|
1133
|
+
{
|
|
1134
|
+
sortConfig,
|
|
1135
|
+
fieldLabel: getFieldLabel(sortConfig.field),
|
|
1136
|
+
onDirectionChange: (direction) => handleDirectionChange(sortConfig.field, direction),
|
|
1137
|
+
onDelete: () => handleDeleteField(sortConfig.field)
|
|
1138
|
+
},
|
|
1139
|
+
sortConfig.field
|
|
1140
|
+
))
|
|
1141
|
+
}
|
|
1142
|
+
)
|
|
1143
|
+
}
|
|
1144
|
+
) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_empty_sort_fields text-center py-8 text-sm text-muted-foreground", children: 'No sort fields added. Click "Add field" to add sorting criteria.' })
|
|
1145
|
+
] }),
|
|
1146
|
+
/* @__PURE__ */ jsxRuntime.jsxs(DialogFooter, { children: [
|
|
1147
|
+
sortFields.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1148
|
+
Button,
|
|
1149
|
+
{
|
|
1150
|
+
variant: "outline",
|
|
1151
|
+
onClick: handleClearAll,
|
|
1152
|
+
className: "cls_clear_all_btn",
|
|
1153
|
+
children: [
|
|
1154
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "cls_clear_all_icon h-4 w-4 mr-2" }),
|
|
1155
|
+
"Clear All"
|
|
1156
|
+
]
|
|
1157
|
+
}
|
|
1158
|
+
),
|
|
824
1159
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
825
1160
|
Button,
|
|
826
1161
|
{
|
|
@@ -845,5 +1180,6 @@ function MultiFilterDialog({
|
|
|
845
1180
|
|
|
846
1181
|
exports.ExampleComponent = ExampleComponent;
|
|
847
1182
|
exports.MultiFilterDialog = MultiFilterDialog;
|
|
1183
|
+
exports.MultiSortDialog = MultiSortDialog;
|
|
848
1184
|
//# sourceMappingURL=index.cjs.map
|
|
849
1185
|
//# sourceMappingURL=index.cjs.map
|