doom-design-system 0.6.0 → 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/FileUpload/FileUpload.module.css +18 -14
- 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 +3 -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
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
@layer doom.components {
|
|
2
|
+
.rating {
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
gap: var(--control-gap);
|
|
6
|
+
position: relative;
|
|
7
|
+
}
|
|
8
|
+
.rating.disabled {
|
|
9
|
+
opacity: 0.6;
|
|
10
|
+
cursor: not-allowed !important;
|
|
11
|
+
background-image: repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(0, 0, 0, 0.05) 10px, rgba(0, 0, 0, 0.05) 20px) !important;
|
|
12
|
+
}
|
|
13
|
+
.rating.disabled:hover {
|
|
14
|
+
transform: none !important;
|
|
15
|
+
filter: none !important;
|
|
16
|
+
}
|
|
17
|
+
.rating.disabled {
|
|
18
|
+
cursor: not-allowed;
|
|
19
|
+
}
|
|
20
|
+
.sm {
|
|
21
|
+
gap: var(--space-1);
|
|
22
|
+
}
|
|
23
|
+
.lg {
|
|
24
|
+
gap: var(--space-2);
|
|
25
|
+
}
|
|
26
|
+
.iconWrapper {
|
|
27
|
+
position: relative;
|
|
28
|
+
display: inline-flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
}
|
|
32
|
+
.icon {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
transition: transform var(--duration-fast) var(--ease-in-out), color var(--duration-fast) var(--ease-in-out);
|
|
35
|
+
}
|
|
36
|
+
.filled {
|
|
37
|
+
color: var(--warning);
|
|
38
|
+
}
|
|
39
|
+
.unfilled {
|
|
40
|
+
color: var(--muted-foreground);
|
|
41
|
+
}
|
|
42
|
+
.filledOverlay {
|
|
43
|
+
position: absolute;
|
|
44
|
+
inset: 0;
|
|
45
|
+
display: inline-flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
pointer-events: none;
|
|
49
|
+
}
|
|
50
|
+
.halfClip {
|
|
51
|
+
clip-path: inset(0 50% 0 0);
|
|
52
|
+
}
|
|
53
|
+
.iconButton {
|
|
54
|
+
display: inline-flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
justify-content: center;
|
|
57
|
+
padding: 0;
|
|
58
|
+
border: none;
|
|
59
|
+
background: none;
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
transition: transform var(--duration-fast) var(--ease-in-out), color var(--duration-fast) var(--ease-in-out);
|
|
62
|
+
}
|
|
63
|
+
.iconButton:focus-visible:focus-visible {
|
|
64
|
+
outline: none;
|
|
65
|
+
transform: translate(calc(-1 * var(--space-0\.5)), calc(-1 * var(--space-0\.5)));
|
|
66
|
+
box-shadow: var(--shadow-offset-xl) var(--shadow-primary);
|
|
67
|
+
border-color: var(--primary);
|
|
68
|
+
}
|
|
69
|
+
.iconButton:focus-visible:focus {
|
|
70
|
+
outline: none;
|
|
71
|
+
}
|
|
72
|
+
.iconButton:focus-visible {
|
|
73
|
+
border-radius: var(--radius-md);
|
|
74
|
+
}
|
|
75
|
+
.iconButton:focus {
|
|
76
|
+
outline: none;
|
|
77
|
+
}
|
|
78
|
+
.iconButton:hover {
|
|
79
|
+
transform: scale(1.15);
|
|
80
|
+
}
|
|
81
|
+
.iconButton:active {
|
|
82
|
+
transform: scale(0.85);
|
|
83
|
+
}
|
|
84
|
+
.iconButton:disabled {
|
|
85
|
+
cursor: not-allowed;
|
|
86
|
+
pointer-events: none;
|
|
87
|
+
}
|
|
88
|
+
.radioButton {
|
|
89
|
+
position: absolute;
|
|
90
|
+
top: 0;
|
|
91
|
+
bottom: 0;
|
|
92
|
+
padding: 0;
|
|
93
|
+
border: none;
|
|
94
|
+
background: none;
|
|
95
|
+
cursor: pointer;
|
|
96
|
+
z-index: 1;
|
|
97
|
+
}
|
|
98
|
+
.radioButton:focus-visible:focus-visible {
|
|
99
|
+
outline: none;
|
|
100
|
+
transform: translate(calc(-1 * var(--space-0\.5)), calc(-1 * var(--space-0\.5)));
|
|
101
|
+
box-shadow: var(--shadow-offset-xl) var(--shadow-primary);
|
|
102
|
+
border-color: var(--primary);
|
|
103
|
+
}
|
|
104
|
+
.radioButton:focus-visible:focus {
|
|
105
|
+
outline: none;
|
|
106
|
+
}
|
|
107
|
+
.radioButton:focus-visible {
|
|
108
|
+
border-radius: var(--radius-md);
|
|
109
|
+
}
|
|
110
|
+
.radioButton:focus {
|
|
111
|
+
outline: none;
|
|
112
|
+
}
|
|
113
|
+
.radioButton:disabled {
|
|
114
|
+
cursor: not-allowed;
|
|
115
|
+
pointer-events: none;
|
|
116
|
+
}
|
|
117
|
+
.halfLeft {
|
|
118
|
+
left: 0;
|
|
119
|
+
width: 50%;
|
|
120
|
+
}
|
|
121
|
+
.halfRight {
|
|
122
|
+
right: 0;
|
|
123
|
+
width: 50%;
|
|
124
|
+
}
|
|
125
|
+
.iconWrapper:hover .icon {
|
|
126
|
+
transform: scale(1.15);
|
|
127
|
+
}
|
|
128
|
+
.iconWrapper:active .icon {
|
|
129
|
+
transform: scale(0.85);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Rating";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Rating.js";
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ColumnDef, Row } from "@tanstack/react-table";
|
|
2
2
|
import React from "react";
|
|
3
|
-
import { type FilterOperatorKey } from "
|
|
4
|
-
import type { FilterGroupItem } from "./FilterBuilder/FilterGroup";
|
|
3
|
+
import { type FilterDraftGroup, type FilterOperatorKey } from "../../lib/filter";
|
|
5
4
|
export interface TableProps<T> {
|
|
6
5
|
data: T[];
|
|
7
6
|
/**
|
|
@@ -16,7 +15,7 @@ export interface TableProps<T> {
|
|
|
16
15
|
enableSorting?: boolean;
|
|
17
16
|
enableVirtualization?: boolean;
|
|
18
17
|
enableAdvancedFiltering?: boolean;
|
|
19
|
-
onAdvancedFilterChange?: (value:
|
|
18
|
+
onAdvancedFilterChange?: (value: FilterDraftGroup) => void;
|
|
20
19
|
pageSize?: number;
|
|
21
20
|
height?: string | number;
|
|
22
21
|
maxHeight?: string | number;
|
|
@@ -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;
|