doom-design-system 0.6.1 → 0.7.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/dist/components/A2UI/catalog.js +98 -0
- package/dist/components/A2UI/mapping.js +5 -0
- package/dist/components/Checkbox/Checkbox.d.ts +1 -0
- package/dist/components/Checkbox/Checkbox.js +20 -4
- package/dist/components/FileUpload/FileUpload.js +2 -1
- package/dist/components/Page/Page.module.css +9 -3
- package/dist/components/Popover/Popover.d.ts +1 -1
- package/dist/components/Popover/Popover.js +53 -23
- package/dist/components/Rating/Rating.d.ts +17 -0
- package/dist/components/Rating/Rating.js +126 -0
- package/dist/components/Rating/Rating.module.css +131 -0
- package/dist/components/Rating/index.d.ts +1 -0
- package/dist/components/Rating/index.js +1 -0
- package/dist/components/Table/Table.d.ts +2 -3
- package/dist/components/Table/Table.js +2 -20
- package/dist/components/ToggleGroup/ToggleGroup.d.ts +22 -0
- package/dist/components/ToggleGroup/ToggleGroup.js +157 -0
- package/dist/components/ToggleGroup/ToggleGroup.module.css +81 -0
- package/dist/components/ToggleGroup/index.d.ts +1 -0
- package/dist/components/ToggleGroup/index.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/lib/filter/ast/array-filter.d.ts +7 -0
- package/dist/lib/filter/ast/array-filter.js +16 -0
- package/dist/lib/filter/ast/evaluate.d.ts +6 -0
- package/dist/lib/filter/ast/evaluate.js +35 -0
- package/dist/lib/filter/ast/index.d.ts +5 -0
- package/dist/lib/filter/ast/index.js +4 -0
- package/dist/lib/filter/ast/operators.d.ts +7 -0
- package/dist/{components/Table/utils/filterAst.js → lib/filter/ast/operators.js} +0 -52
- package/dist/lib/filter/ast/simple.d.ts +7 -0
- package/dist/lib/filter/ast/simple.js +26 -0
- package/dist/lib/filter/ast/types.d.ts +24 -0
- package/dist/lib/filter/ast/types.js +1 -0
- package/dist/lib/filter/index.d.ts +7 -0
- package/dist/lib/filter/index.js +7 -0
- package/dist/lib/filter/ui/FilterBuilder.d.ts +25 -0
- package/dist/{components/Table/FilterBuilder → lib/filter/ui}/FilterBuilder.js +3 -3
- package/dist/{components/Table/FilterBuilder → lib/filter/ui}/FilterConditionRow.d.ts +1 -1
- package/dist/{components/Table/FilterBuilder → lib/filter/ui}/FilterConditionRow.js +4 -4
- package/dist/{components/Table/FilterBuilder → lib/filter/ui}/FilterGroup.d.ts +9 -9
- package/dist/{components/Table/FilterBuilder → lib/filter/ui}/FilterGroup.js +7 -7
- package/dist/{components/Table/FilterBuilder → lib/filter/ui}/FilterSheetNested.d.ts +3 -3
- package/dist/{components/Table/FilterBuilder → lib/filter/ui}/FilterSheetNested.js +4 -4
- package/dist/lib/filter/ui/convert.d.ts +16 -0
- package/dist/lib/filter/ui/convert.js +60 -0
- package/dist/lib/filter/ui/index.d.ts +5 -0
- package/dist/lib/filter/ui/index.js +5 -0
- package/dist/lib/filter/ui/utils/tree-utils.d.ts +15 -0
- package/dist/styles/globals.css +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/vitest.config.js +6 -1
- package/package.json +10 -3
- package/dist/components/Table/FilterBuilder/FilterBuilder.d.ts +0 -20
- package/dist/components/Table/FilterBuilder/utils/tree-utils.d.ts +0 -15
- package/dist/components/Table/utils/arrayFilter.d.ts +0 -7
- package/dist/components/Table/utils/arrayFilter.js +0 -21
- package/dist/components/Table/utils/filterAst.d.ts +0 -33
- /package/dist/{components/Table/FilterBuilder → lib/filter/ui}/FilterBuilder.module.css +0 -0
- /package/dist/{components/Table/FilterBuilder → lib/filter/ui}/FilterConditionRow.module.css +0 -0
- /package/dist/{components/Table/FilterBuilder → lib/filter/ui}/FilterGroup.module.css +0 -0
- /package/dist/{components/Table/FilterBuilder → lib/filter/ui}/FilterSheet.module.css +0 -0
- /package/dist/{components/Table/FilterBuilder → lib/filter/ui}/utils/tree-utils.js +0 -0
|
@@ -4,38 +4,20 @@ import { useVirtualizer } from "@tanstack/react-virtual";
|
|
|
4
4
|
import clsx from "clsx";
|
|
5
5
|
import { ChevronRight, Filter, ListFilter, Search } from "lucide-react";
|
|
6
6
|
import React, { useMemo, useState } from "react";
|
|
7
|
+
import { countConditions, draftToFilter, evaluateFilter, FilterSheetNested, } from "../../lib/filter/index.js";
|
|
7
8
|
import { Button } from "../Button/Button.js";
|
|
8
9
|
import { Chip } from "../Chip/Chip.js";
|
|
9
10
|
import { Input } from "../Input/Input.js";
|
|
10
11
|
import { Flex } from "../Layout/Layout.js";
|
|
11
12
|
import { Pagination } from "../Pagination/Pagination.js";
|
|
12
13
|
import { Select } from "../Select/Select.js";
|
|
13
|
-
import { countConditions, } from "./FilterBuilder/FilterBuilder.js";
|
|
14
|
-
import { FilterSheetNested } from "./FilterBuilder/FilterSheetNested.js";
|
|
15
14
|
import styles from "./Table.module.css";
|
|
16
15
|
import { TableHeaderFilter } from "./TableHeaderFilter.js";
|
|
17
|
-
import { evaluateFilter } from "./utils/filterAst.js";
|
|
18
16
|
const coreRowModel = getCoreRowModel();
|
|
19
17
|
const sortedRowModel = getSortedRowModel();
|
|
20
18
|
const paginationRowModel = getPaginationRowModel();
|
|
21
19
|
const filteredRowModel = getFilteredRowModel();
|
|
22
20
|
const expandedRowModel = getExpandedRowModel();
|
|
23
|
-
const convertToFilterNode = (item) => {
|
|
24
|
-
if (item.type === "group") {
|
|
25
|
-
return {
|
|
26
|
-
type: "group",
|
|
27
|
-
logic: item.logic,
|
|
28
|
-
conditions: (item.children || []).map(convertToFilterNode),
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
return {
|
|
32
|
-
type: "condition",
|
|
33
|
-
field: item.field,
|
|
34
|
-
operator: item.operator,
|
|
35
|
-
value: item.value,
|
|
36
|
-
logic: item.logic,
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
21
|
function VirtualTableBody({ table, columns, striped, density, scrollElement, onRowClick, renderExpandedRow, totalColumns, }) {
|
|
40
22
|
const { rows } = table.getRowModel();
|
|
41
23
|
const rowVirtualizer = useVirtualizer({
|
|
@@ -94,7 +76,7 @@ export function Table({ data, columns, enablePagination = true, enableFiltering
|
|
|
94
76
|
if (!advancedFilterValue || !enableAdvancedFiltering) {
|
|
95
77
|
return data;
|
|
96
78
|
}
|
|
97
|
-
const filterNode =
|
|
79
|
+
const filterNode = draftToFilter(advancedFilterValue);
|
|
98
80
|
if (filterNode.type === "group" && filterNode.conditions.length === 0) {
|
|
99
81
|
return data;
|
|
100
82
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ControlSize } from "../../styles/types";
|
|
3
|
+
export interface ToggleGroupProps {
|
|
4
|
+
type: "single" | "multiple";
|
|
5
|
+
value?: string | string[];
|
|
6
|
+
defaultValue?: string | string[];
|
|
7
|
+
onValueChange?: (value: string | string[]) => void;
|
|
8
|
+
size?: ControlSize;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
className?: string;
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
"aria-label"?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function ToggleGroup({ type, value: controlledValue, defaultValue, onValueChange, size, disabled, className, children, "aria-label": ariaLabel, }: ToggleGroupProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export interface ToggleGroupItemProps {
|
|
16
|
+
value: string;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
"aria-label"?: string;
|
|
19
|
+
className?: string;
|
|
20
|
+
children: React.ReactNode;
|
|
21
|
+
}
|
|
22
|
+
export declare function ToggleGroupItem({ value, disabled: itemDisabled, "aria-label": ariaLabel, className, children, }: ToggleGroupItemProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import { createContext, useCallback, useContext, useRef, useState, } from "react";
|
|
5
|
+
import styles from "./ToggleGroup.module.css";
|
|
6
|
+
const ToggleGroupContext = createContext(null);
|
|
7
|
+
export function ToggleGroup({ type, value: controlledValue, defaultValue, onValueChange, size = "md", disabled = false, className, children, "aria-label": ariaLabel, }) {
|
|
8
|
+
const [internalValue, setInternalValue] = useState(defaultValue !== null && defaultValue !== void 0 ? defaultValue : (type === "multiple" ? [] : ""));
|
|
9
|
+
const isControlled = controlledValue !== undefined;
|
|
10
|
+
const activeValue = isControlled ? controlledValue : internalValue;
|
|
11
|
+
const itemRefs = useRef(new Map());
|
|
12
|
+
const itemOrder = useRef([]);
|
|
13
|
+
const [focusedValue, setFocusedValue] = useState(null);
|
|
14
|
+
const [items, setItems] = useState([]);
|
|
15
|
+
const toggle = useCallback((itemValue) => {
|
|
16
|
+
let nextValue;
|
|
17
|
+
if (type === "single") {
|
|
18
|
+
const current = (typeof activeValue === "string" ? activeValue : "");
|
|
19
|
+
nextValue = current === itemValue ? "" : itemValue;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const arr = Array.isArray(activeValue) ? activeValue : [];
|
|
23
|
+
nextValue = arr.includes(itemValue)
|
|
24
|
+
? arr.filter((v) => v !== itemValue)
|
|
25
|
+
: [...arr, itemValue];
|
|
26
|
+
}
|
|
27
|
+
if (!isControlled) {
|
|
28
|
+
setInternalValue(nextValue);
|
|
29
|
+
}
|
|
30
|
+
onValueChange === null || onValueChange === void 0 ? void 0 : onValueChange(nextValue);
|
|
31
|
+
}, [type, activeValue, isControlled, onValueChange]);
|
|
32
|
+
const registerItem = useCallback((ref, value, itemDisabled) => {
|
|
33
|
+
if (ref) {
|
|
34
|
+
itemRefs.current.set(value, ref);
|
|
35
|
+
if (!itemOrder.current.includes(value)) {
|
|
36
|
+
itemOrder.current.push(value);
|
|
37
|
+
}
|
|
38
|
+
setItems((prev) => {
|
|
39
|
+
const existing = prev.find((it) => it.value === value);
|
|
40
|
+
if (existing && existing.disabled === itemDisabled) {
|
|
41
|
+
return prev;
|
|
42
|
+
}
|
|
43
|
+
const filtered = prev.filter((it) => it.value !== value);
|
|
44
|
+
const idx = itemOrder.current.indexOf(value);
|
|
45
|
+
filtered.splice(idx, 0, { value, disabled: itemDisabled });
|
|
46
|
+
return filtered;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}, []);
|
|
50
|
+
const unregisterItem = useCallback((value) => {
|
|
51
|
+
itemRefs.current.delete(value);
|
|
52
|
+
itemOrder.current = itemOrder.current.filter((v) => v !== value);
|
|
53
|
+
setItems((prev) => prev.filter((it) => it.value !== value));
|
|
54
|
+
}, []);
|
|
55
|
+
const getEnabledItems = useCallback(() => {
|
|
56
|
+
return itemOrder.current.filter((v) => {
|
|
57
|
+
const el = itemRefs.current.get(v);
|
|
58
|
+
return el && !el.disabled;
|
|
59
|
+
});
|
|
60
|
+
}, []);
|
|
61
|
+
// Compute the tabbable item: focused > pressed > first enabled
|
|
62
|
+
const tabbableValue = (() => {
|
|
63
|
+
var _a;
|
|
64
|
+
const enabledValues = items
|
|
65
|
+
.filter((it) => !it.disabled)
|
|
66
|
+
.map((it) => it.value);
|
|
67
|
+
if (enabledValues.length === 0) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
if (focusedValue && enabledValues.includes(focusedValue)) {
|
|
71
|
+
return focusedValue;
|
|
72
|
+
}
|
|
73
|
+
if (type === "single") {
|
|
74
|
+
const pressed = typeof activeValue === "string" ? activeValue : "";
|
|
75
|
+
if (pressed && enabledValues.includes(pressed)) {
|
|
76
|
+
return pressed;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
const arr = Array.isArray(activeValue) ? activeValue : [];
|
|
81
|
+
const firstPressed = arr.find((v) => enabledValues.includes(v));
|
|
82
|
+
if (firstPressed) {
|
|
83
|
+
return firstPressed;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return (_a = enabledValues[0]) !== null && _a !== void 0 ? _a : null;
|
|
87
|
+
})();
|
|
88
|
+
const handleKeyDown = useCallback((e, _value) => {
|
|
89
|
+
const enabledItems = getEnabledItems();
|
|
90
|
+
if (enabledItems.length === 0) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const currentEl = e.currentTarget;
|
|
94
|
+
const currentValue = currentEl.getAttribute("data-value") || "";
|
|
95
|
+
const currentIndex = enabledItems.indexOf(currentValue);
|
|
96
|
+
let nextIndex = null;
|
|
97
|
+
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
nextIndex = (currentIndex + 1) % enabledItems.length;
|
|
100
|
+
}
|
|
101
|
+
else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
nextIndex =
|
|
104
|
+
(currentIndex - 1 + enabledItems.length) % enabledItems.length;
|
|
105
|
+
}
|
|
106
|
+
if (nextIndex !== null) {
|
|
107
|
+
const nextVal = enabledItems[nextIndex];
|
|
108
|
+
setFocusedValue(nextVal);
|
|
109
|
+
const nextEl = itemRefs.current.get(nextVal);
|
|
110
|
+
nextEl === null || nextEl === void 0 ? void 0 : nextEl.focus();
|
|
111
|
+
}
|
|
112
|
+
}, [getEnabledItems]);
|
|
113
|
+
return (_jsx(ToggleGroupContext.Provider, { value: {
|
|
114
|
+
activeValue,
|
|
115
|
+
toggle,
|
|
116
|
+
type,
|
|
117
|
+
size,
|
|
118
|
+
disabled,
|
|
119
|
+
registerItem,
|
|
120
|
+
unregisterItem,
|
|
121
|
+
handleKeyDown,
|
|
122
|
+
tabbableValue,
|
|
123
|
+
}, children: _jsx("div", { "aria-label": ariaLabel, className: clsx(styles.toggleGroup, className), role: "group", children: children }) }));
|
|
124
|
+
}
|
|
125
|
+
export function ToggleGroupItem({ value, disabled: itemDisabled, "aria-label": ariaLabel, className, children, }) {
|
|
126
|
+
const context = useContext(ToggleGroupContext);
|
|
127
|
+
if (!context) {
|
|
128
|
+
throw new Error("ToggleGroupItem must be used within <ToggleGroup>");
|
|
129
|
+
}
|
|
130
|
+
const isDisabled = itemDisabled || context.disabled;
|
|
131
|
+
const isPressed = context.type === "single"
|
|
132
|
+
? context.activeValue === value
|
|
133
|
+
: Array.isArray(context.activeValue) &&
|
|
134
|
+
context.activeValue.includes(value);
|
|
135
|
+
const refCallback = useCallback((el) => {
|
|
136
|
+
if (el) {
|
|
137
|
+
context.registerItem(el, value, isDisabled);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
context.unregisterItem(value);
|
|
141
|
+
}
|
|
142
|
+
}, [value, isDisabled, context.registerItem, context.unregisterItem]);
|
|
143
|
+
const handleClick = () => {
|
|
144
|
+
if (!isDisabled) {
|
|
145
|
+
context.toggle(value);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
const handleKeyDown = (e) => {
|
|
149
|
+
if (e.key === "ArrowRight" ||
|
|
150
|
+
e.key === "ArrowDown" ||
|
|
151
|
+
e.key === "ArrowLeft" ||
|
|
152
|
+
e.key === "ArrowUp") {
|
|
153
|
+
context.handleKeyDown(e, value);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
return (_jsx("button", { ref: refCallback, "aria-label": ariaLabel, "aria-pressed": isPressed, className: clsx(styles.toggleGroupItem, styles[context.size], isPressed && styles.pressed, isDisabled && styles.disabled, className), "data-value": value, disabled: isDisabled, tabIndex: context.tabbableValue === value ? 0 : -1, type: "button", onClick: handleClick, onKeyDown: handleKeyDown, children: children }));
|
|
157
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
@layer doom.components {
|
|
2
|
+
.toggleGroup {
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
align-items: stretch;
|
|
5
|
+
border: var(--surface-border-width) solid var(--card-border);
|
|
6
|
+
border-radius: var(--control-radius);
|
|
7
|
+
box-shadow: var(--shadow-sm);
|
|
8
|
+
}
|
|
9
|
+
.toggleGroupItem {
|
|
10
|
+
display: inline-flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
gap: var(--control-gap);
|
|
14
|
+
font-weight: 700;
|
|
15
|
+
text-transform: uppercase;
|
|
16
|
+
letter-spacing: 0.05em;
|
|
17
|
+
cursor: pointer;
|
|
18
|
+
background: var(--card-bg);
|
|
19
|
+
color: var(--foreground);
|
|
20
|
+
height: var(--control-height);
|
|
21
|
+
padding: var(--control-padding-y) var(--control-padding-x);
|
|
22
|
+
font-size: var(--control-font-size);
|
|
23
|
+
border: none;
|
|
24
|
+
border-left: var(--surface-border-width) solid var(--card-border);
|
|
25
|
+
border-radius: 0;
|
|
26
|
+
}
|
|
27
|
+
.toggleGroupItem:first-child {
|
|
28
|
+
border-left: none;
|
|
29
|
+
border-radius: var(--radius-sm) 0 0 var(--radius-sm);
|
|
30
|
+
}
|
|
31
|
+
.toggleGroupItem:last-child {
|
|
32
|
+
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
|
|
33
|
+
}
|
|
34
|
+
.toggleGroupItem:first-child:last-child {
|
|
35
|
+
border-radius: var(--radius-sm);
|
|
36
|
+
}
|
|
37
|
+
.toggleGroupItem:not(.pressed):not(.disabled):hover {
|
|
38
|
+
background: color-mix(in srgb, var(--primary), transparent 85%);
|
|
39
|
+
}
|
|
40
|
+
.toggleGroupItem:focus-visible {
|
|
41
|
+
box-shadow: inset 0 0 0 var(--surface-border-width) var(--primary);
|
|
42
|
+
z-index: 1;
|
|
43
|
+
}
|
|
44
|
+
.toggleGroupItem:focus {
|
|
45
|
+
outline: none;
|
|
46
|
+
}
|
|
47
|
+
.toggleGroupItem.pressed {
|
|
48
|
+
background: var(--primary);
|
|
49
|
+
color: var(--primary-foreground);
|
|
50
|
+
}
|
|
51
|
+
.toggleGroupItem.sm {
|
|
52
|
+
--control-height: var(--size-7);
|
|
53
|
+
--control-padding-x: var(--space-2);
|
|
54
|
+
--control-padding-y: var(--space-1);
|
|
55
|
+
--control-font-size: var(--text-xs);
|
|
56
|
+
--control-icon-size: var(--size-4);
|
|
57
|
+
--control-gap: var(--space-1);
|
|
58
|
+
--control-radius: var(--radius-sm);
|
|
59
|
+
}
|
|
60
|
+
.toggleGroupItem.lg {
|
|
61
|
+
--control-height: var(--size-10);
|
|
62
|
+
--control-padding-x: var(--space-4);
|
|
63
|
+
--control-padding-y: var(--space-2);
|
|
64
|
+
--control-font-size: var(--text-base);
|
|
65
|
+
--control-icon-size: var(--size-5);
|
|
66
|
+
--control-gap: var(--space-2);
|
|
67
|
+
--control-radius: var(--radius-md);
|
|
68
|
+
}
|
|
69
|
+
.toggleGroupItem.disabled {
|
|
70
|
+
opacity: 0.6;
|
|
71
|
+
cursor: not-allowed !important;
|
|
72
|
+
background-image: repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(0, 0, 0, 0.05) 10px, rgba(0, 0, 0, 0.05) 20px) !important;
|
|
73
|
+
}
|
|
74
|
+
.toggleGroupItem.disabled:hover {
|
|
75
|
+
transform: none !important;
|
|
76
|
+
filter: none !important;
|
|
77
|
+
}
|
|
78
|
+
.toggleGroupItem.disabled {
|
|
79
|
+
cursor: not-allowed;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./ToggleGroup";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./ToggleGroup.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export * from "./components/Pagination";
|
|
|
28
28
|
export * from "./components/Popover";
|
|
29
29
|
export * from "./components/ProgressBar";
|
|
30
30
|
export * from "./components/RadioGroup";
|
|
31
|
+
export * from "./components/Rating";
|
|
31
32
|
export * from "./components/Select";
|
|
32
33
|
export * from "./components/Sheet";
|
|
33
34
|
export * from "./components/Sidebar";
|
|
@@ -42,6 +43,7 @@ export * from "./components/Tabs";
|
|
|
42
43
|
export * from "./components/Text";
|
|
43
44
|
export * from "./components/Textarea";
|
|
44
45
|
export * from "./components/Toast";
|
|
46
|
+
export * from "./components/ToggleGroup";
|
|
45
47
|
export * from "./components/Tooltip";
|
|
46
48
|
export * from "./DesignSystemProvider";
|
|
47
49
|
export * from "./styles/themes";
|
package/dist/index.js
CHANGED
|
@@ -28,6 +28,7 @@ export * from "./components/Pagination/index.js";
|
|
|
28
28
|
export * from "./components/Popover/index.js";
|
|
29
29
|
export * from "./components/ProgressBar/index.js";
|
|
30
30
|
export * from "./components/RadioGroup/index.js";
|
|
31
|
+
export * from "./components/Rating/index.js";
|
|
31
32
|
export * from "./components/Select/index.js";
|
|
32
33
|
export * from "./components/Sheet/index.js";
|
|
33
34
|
export * from "./components/Sidebar/index.js";
|
|
@@ -42,6 +43,7 @@ export * from "./components/Tabs/index.js";
|
|
|
42
43
|
export * from "./components/Text/index.js";
|
|
43
44
|
export * from "./components/Textarea/index.js";
|
|
44
45
|
export * from "./components/Toast/index.js";
|
|
46
|
+
export * from "./components/ToggleGroup/index.js";
|
|
45
47
|
export * from "./components/Tooltip/index.js";
|
|
46
48
|
export * from "./DesignSystemProvider.js";
|
|
47
49
|
export * from "./styles/themes/index.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FilterFn } from "@tanstack/react-table";
|
|
2
|
+
/**
|
|
3
|
+
* TanStack Table FilterFn that matches a row when its column value appears
|
|
4
|
+
* in the supplied filter array. Values are compared as strings to support
|
|
5
|
+
* typed columns.
|
|
6
|
+
*/
|
|
7
|
+
export declare const arrayIncludesFilter: FilterFn<unknown>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TanStack Table FilterFn that matches a row when its column value appears
|
|
3
|
+
* in the supplied filter array. Values are compared as strings to support
|
|
4
|
+
* typed columns.
|
|
5
|
+
*/
|
|
6
|
+
export const arrayIncludesFilter = (row, columnId, filterValue) => {
|
|
7
|
+
if (!filterValue || !Array.isArray(filterValue) || filterValue.length === 0) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
const cellValue = row.getValue(columnId);
|
|
11
|
+
if (cellValue === null || cellValue === undefined) {
|
|
12
|
+
return filterValue.includes("");
|
|
13
|
+
}
|
|
14
|
+
return filterValue.includes(String(cellValue));
|
|
15
|
+
};
|
|
16
|
+
arrayIncludesFilter.autoRemove = (val) => !val || !Array.isArray(val) || val.length === 0;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { OPERATORS } from "./operators.js";
|
|
2
|
+
/**
|
|
3
|
+
* Recursively evaluates a Filter against a row of data.
|
|
4
|
+
* Returns true if the row matches the filter expression.
|
|
5
|
+
*/
|
|
6
|
+
export function evaluateFilter(filter, row) {
|
|
7
|
+
if (filter.type === "condition") {
|
|
8
|
+
const { field, operator, value } = filter;
|
|
9
|
+
const cellValue = row[field];
|
|
10
|
+
const operatorDef = OPERATORS[operator];
|
|
11
|
+
if (!operatorDef) {
|
|
12
|
+
console.warn(`Unknown operator: ${operator}`);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
return operatorDef.fn(cellValue, value);
|
|
16
|
+
}
|
|
17
|
+
// Group: short-circuit on empty, otherwise fold conditions left-to-right
|
|
18
|
+
// respecting each child's `logic` (and | or). Implicit "and" when omitted.
|
|
19
|
+
const { conditions } = filter;
|
|
20
|
+
if (!conditions || conditions.length === 0) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
let result = evaluateFilter(conditions[0], row);
|
|
24
|
+
for (let i = 1; i < conditions.length; i++) {
|
|
25
|
+
const next = conditions[i];
|
|
26
|
+
const nextResult = evaluateFilter(next, row);
|
|
27
|
+
if (next.logic === "or") {
|
|
28
|
+
result = result || nextResult;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
result = result && nextResult;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { arrayIncludesFilter } from "./array-filter";
|
|
2
|
+
export { evaluateFilter } from "./evaluate";
|
|
3
|
+
export { type FilterOperatorDef, OPERATORS } from "./operators";
|
|
4
|
+
export { simpleFiltersToFilter } from "./simple";
|
|
5
|
+
export type { Filter, FilterCondition, FilterGroup, FilterOperatorKey, } from "./types";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FilterOperatorKey } from "./types";
|
|
2
|
+
export interface FilterOperatorDef {
|
|
3
|
+
readonly key: FilterOperatorKey;
|
|
4
|
+
readonly label: string;
|
|
5
|
+
readonly fn: (cellValue: unknown, filterValue: unknown) => boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare const OPERATORS: Readonly<Record<FilterOperatorKey, FilterOperatorDef>>;
|
|
@@ -65,55 +65,3 @@ export const OPERATORS = {
|
|
|
65
65
|
fn: (a) => a != null && a !== "",
|
|
66
66
|
},
|
|
67
67
|
};
|
|
68
|
-
export function evaluateFilter(node, row) {
|
|
69
|
-
if (node.type === "condition") {
|
|
70
|
-
const { field, operator, value } = node;
|
|
71
|
-
const cellValue = row[field];
|
|
72
|
-
const operatorDef = OPERATORS[operator];
|
|
73
|
-
if (!operatorDef) {
|
|
74
|
-
console.warn(`Unknown operator: ${operator}`);
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
return operatorDef.fn(cellValue, value);
|
|
78
|
-
}
|
|
79
|
-
if (node.type === "group") {
|
|
80
|
-
const { conditions } = node;
|
|
81
|
-
if (!conditions || conditions.length === 0) {
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
let accumulatedResult = evaluateFilter(conditions[0], row);
|
|
85
|
-
for (let i = 1; i < conditions.length; i++) {
|
|
86
|
-
const current = conditions[i];
|
|
87
|
-
const result = evaluateFilter(current, row);
|
|
88
|
-
if (current.logic === "or") {
|
|
89
|
-
accumulatedResult = accumulatedResult || result;
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
accumulatedResult = accumulatedResult && result;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return accumulatedResult;
|
|
96
|
-
}
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
99
|
-
export function simpleFiltersToAST(selections) {
|
|
100
|
-
const conditions = Object.entries(selections)
|
|
101
|
-
.filter(([, value]) => value !== "" && value != null)
|
|
102
|
-
.map(([field, value], index) => ({
|
|
103
|
-
type: "condition",
|
|
104
|
-
field,
|
|
105
|
-
operator: "eq",
|
|
106
|
-
value,
|
|
107
|
-
logic: index > 0 ? "and" : undefined,
|
|
108
|
-
}));
|
|
109
|
-
if (conditions.length === 0) {
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
if (conditions.length === 1) {
|
|
113
|
-
return conditions[0];
|
|
114
|
-
}
|
|
115
|
-
return {
|
|
116
|
-
type: "group",
|
|
117
|
-
conditions,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Filter } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Convenience helper that converts a flat `{ field: value }` map to a Filter
|
|
4
|
+
* tree using equality operators joined by AND. Returns null when no
|
|
5
|
+
* non-empty values are present.
|
|
6
|
+
*/
|
|
7
|
+
export declare function simpleFiltersToFilter(selections: Record<string, string>): Filter | null;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convenience helper that converts a flat `{ field: value }` map to a Filter
|
|
3
|
+
* tree using equality operators joined by AND. Returns null when no
|
|
4
|
+
* non-empty values are present.
|
|
5
|
+
*/
|
|
6
|
+
export function simpleFiltersToFilter(selections) {
|
|
7
|
+
const conditions = Object.entries(selections)
|
|
8
|
+
.filter(([, value]) => value !== "" && value != null)
|
|
9
|
+
.map(([field, value], index) => ({
|
|
10
|
+
type: "condition",
|
|
11
|
+
field,
|
|
12
|
+
operator: "eq",
|
|
13
|
+
value,
|
|
14
|
+
logic: index > 0 ? "and" : undefined,
|
|
15
|
+
}));
|
|
16
|
+
if (conditions.length === 0) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
if (conditions.length === 1) {
|
|
20
|
+
return conditions[0];
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
type: "group",
|
|
24
|
+
conditions,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical filter representation. Tree of conditions and nested groups.
|
|
3
|
+
* Treat as immutable — produce a new Filter rather than mutating in place.
|
|
4
|
+
*
|
|
5
|
+
* For the editable working version used by FilterBuilder UI, see FilterDraft.
|
|
6
|
+
*/
|
|
7
|
+
export type Filter = FilterCondition | FilterGroup;
|
|
8
|
+
export interface FilterCondition {
|
|
9
|
+
readonly type: "condition";
|
|
10
|
+
readonly id?: string;
|
|
11
|
+
readonly field: string;
|
|
12
|
+
readonly operator: FilterOperatorKey;
|
|
13
|
+
readonly value: unknown;
|
|
14
|
+
/** Joins this condition with the previous sibling. Defaults to "and" when omitted. */
|
|
15
|
+
readonly logic?: "and" | "or";
|
|
16
|
+
}
|
|
17
|
+
export interface FilterGroup {
|
|
18
|
+
readonly type: "group";
|
|
19
|
+
readonly id?: string;
|
|
20
|
+
readonly conditions: ReadonlyArray<Filter>;
|
|
21
|
+
/** Joins this group with the previous sibling. Defaults to "and" when omitted. */
|
|
22
|
+
readonly logic?: "and" | "or";
|
|
23
|
+
}
|
|
24
|
+
export type FilterOperatorKey = "eq" | "neq" | "contains" | "startsWith" | "endsWith" | "gt" | "gte" | "lt" | "lte" | "in" | "notIn" | "isEmpty" | "isNotEmpty";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { Filter, FilterCondition, FilterGroup, FilterOperatorKey, } from "./ast";
|
|
2
|
+
export { arrayIncludesFilter, evaluateFilter, type FilterOperatorDef, OPERATORS, simpleFiltersToFilter, } from "./ast";
|
|
3
|
+
export { draftToFilter, filterToDraft } from "./ui/convert";
|
|
4
|
+
export { countConditions, FilterBuilder, type FilterBuilderProps, type FilterField, flattenConditions, } from "./ui/FilterBuilder";
|
|
5
|
+
export { type FilterConditionData, FilterConditionRow, } from "./ui/FilterConditionRow";
|
|
6
|
+
export { ConditionRow, type FilterDraft, type FilterDraftCondition, type FilterDraftGroup, FilterGroup as FilterGroupView, } from "./ui/FilterGroup";
|
|
7
|
+
export { FilterSheetNested } from "./ui/FilterSheetNested";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { arrayIncludesFilter, evaluateFilter, OPERATORS, simpleFiltersToFilter, } from "./ast/index.js";
|
|
2
|
+
// UI (presentational) — doom-styled FilterBuilder components
|
|
3
|
+
export { draftToFilter, filterToDraft } from "./ui/convert.js";
|
|
4
|
+
export { countConditions, FilterBuilder, flattenConditions, } from "./ui/FilterBuilder.js";
|
|
5
|
+
export { FilterConditionRow, } from "./ui/FilterConditionRow.js";
|
|
6
|
+
export { ConditionRow, FilterGroup as FilterGroupView, } from "./ui/FilterGroup.js";
|
|
7
|
+
export { FilterSheetNested } from "./ui/FilterSheetNested.js";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type FilterOperatorKey } from "../ast";
|
|
2
|
+
import type { FilterDraftCondition, FilterDraftGroup } from "./FilterGroup";
|
|
3
|
+
/**
|
|
4
|
+
* Describes a column the user can filter on. Provides display metadata
|
|
5
|
+
* (label, input type, select options) and optionally restricts which
|
|
6
|
+
* operators apply.
|
|
7
|
+
*/
|
|
8
|
+
export interface FilterField {
|
|
9
|
+
key: string;
|
|
10
|
+
label: string;
|
|
11
|
+
type: "select" | "text" | "number";
|
|
12
|
+
options?: {
|
|
13
|
+
value: string;
|
|
14
|
+
label: string;
|
|
15
|
+
}[];
|
|
16
|
+
operators?: FilterOperatorKey[];
|
|
17
|
+
}
|
|
18
|
+
export interface FilterBuilderProps {
|
|
19
|
+
fields: FilterField[];
|
|
20
|
+
value: FilterDraftGroup | null;
|
|
21
|
+
onChange: (value: FilterDraftGroup) => void;
|
|
22
|
+
}
|
|
23
|
+
export declare function countConditions(group: FilterDraftGroup): number;
|
|
24
|
+
export declare function flattenConditions(group: FilterDraftGroup): FilterDraftCondition[];
|
|
25
|
+
export declare function FilterBuilder({ fields, value, onChange }: FilterBuilderProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { ListFilter } from "lucide-react";
|
|
4
4
|
import { useState } from "react";
|
|
5
|
-
import { Button } from "
|
|
6
|
-
import { Chip } from "
|
|
7
|
-
import { OPERATORS } from "../
|
|
5
|
+
import { Button } from "../../../components/Button/Button.js";
|
|
6
|
+
import { Chip } from "../../../components/Chip/Chip.js";
|
|
7
|
+
import { OPERATORS } from "../ast/index.js";
|
|
8
8
|
import styles from "./FilterBuilder.module.css";
|
|
9
9
|
import { FilterSheetNested } from "./FilterSheetNested.js";
|
|
10
10
|
export function countConditions(group) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Trash2 } from "lucide-react";
|
|
4
|
-
import { Button } from "
|
|
5
|
-
import { Input } from "
|
|
6
|
-
import { Select } from "
|
|
7
|
-
import { OPERATORS } from "../
|
|
4
|
+
import { Button } from "../../../components/Button/Button.js";
|
|
5
|
+
import { Input } from "../../../components/Input/Input.js";
|
|
6
|
+
import { Select } from "../../../components/Select/Select.js";
|
|
7
|
+
import { OPERATORS } from "../ast/index.js";
|
|
8
8
|
import styles from "./FilterConditionRow.module.css";
|
|
9
9
|
export function FilterConditionRow({ condition, fields, onChange, onRemove, }) {
|
|
10
10
|
var _a;
|