@stll/workspace-ui 0.0.1-placeholder.0 → 0.2.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 +59 -0
- package/dist/calculation-format.d.ts +54 -0
- package/dist/calculation-format.js +72 -0
- package/dist/calculation-selection.d.ts +15 -0
- package/dist/calculation-selection.js +14 -0
- package/dist/calculations.d.ts +67 -0
- package/dist/calculations.js +108 -0
- package/dist/colors.d.ts +2 -0
- package/dist/colors.js +2 -0
- package/dist/conditions/builder.d.ts +98 -0
- package/dist/conditions/builder.js +447 -0
- package/dist/conditions/index.d.ts +3 -0
- package/dist/conditions/index.js +3 -0
- package/dist/conditions/logic.d.ts +70 -0
- package/dist/conditions/logic.js +248 -0
- package/dist/field-value-logic.d.ts +7 -0
- package/dist/field-value-logic.js +8 -0
- package/dist/field-value.d.ts +30 -0
- package/dist/field-value.js +308 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/property-icon.d.ts +12 -0
- package/dist/property-icon.js +56 -0
- package/dist/sorts.d.ts +41 -0
- package/dist/sorts.js +99 -0
- package/dist/table-skeleton-rows.d.ts +18 -0
- package/dist/table-skeleton-rows.js +34 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.js +17 -0
- package/dist/view-switcher.d.ts +33 -0
- package/dist/view-switcher.js +159 -0
- package/dist/view-switcher.logic.d.ts +14 -0
- package/dist/view-switcher.logic.js +14 -0
- package/package.json +135 -3
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
//#region src/conditions/logic.ts
|
|
2
|
+
/**
|
|
3
|
+
* The surface operators the builder exposes. A subset maps to
|
|
4
|
+
* `compare` nodes (binary comparisons against a literal); the rest map
|
|
5
|
+
* to `predicate` nodes. `operatorKind` is the single place that
|
|
6
|
+
* decides which AST node each operator builds.
|
|
7
|
+
*/
|
|
8
|
+
const CONDITION_OPERATORS = [
|
|
9
|
+
"eq",
|
|
10
|
+
"neq",
|
|
11
|
+
"contains",
|
|
12
|
+
"not_contains",
|
|
13
|
+
"starts_with",
|
|
14
|
+
"ends_with",
|
|
15
|
+
"contains_all",
|
|
16
|
+
"in",
|
|
17
|
+
"gt",
|
|
18
|
+
"lt",
|
|
19
|
+
"gte",
|
|
20
|
+
"lte",
|
|
21
|
+
"is_empty",
|
|
22
|
+
"is_not_empty"
|
|
23
|
+
];
|
|
24
|
+
const isConditionOperator = (value) => CONDITION_OPERATORS.some((operator) => operator === value);
|
|
25
|
+
const isCompareOperator = (op) => op === "eq" || op === "neq" || op === "gt" || op === "lt" || op === "gte" || op === "lte";
|
|
26
|
+
/**
|
|
27
|
+
* Default operator labels. A few value types override individual
|
|
28
|
+
* operators with type-aware wording (e.g. `int` shows `=`/`≠`, `date`
|
|
29
|
+
* shows `Is after`/`Is before`). The wording is the host's: it arrives through
|
|
30
|
+
* `ConditionBuilderLabels.operator`, which takes the value type alongside the
|
|
31
|
+
* operator for exactly this reason.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Single source of truth mapping a field's value type to the operators
|
|
35
|
+
* the builder offers for it. Order here is the order rendered in the
|
|
36
|
+
* operator Select, and the first entry is the field's default operator.
|
|
37
|
+
*/
|
|
38
|
+
const OPERATORS_BY_VALUE_TYPE = {
|
|
39
|
+
text: [
|
|
40
|
+
"eq",
|
|
41
|
+
"neq",
|
|
42
|
+
"contains",
|
|
43
|
+
"not_contains",
|
|
44
|
+
"starts_with",
|
|
45
|
+
"ends_with",
|
|
46
|
+
"is_empty",
|
|
47
|
+
"is_not_empty"
|
|
48
|
+
],
|
|
49
|
+
"single-select": [
|
|
50
|
+
"eq",
|
|
51
|
+
"neq",
|
|
52
|
+
"in",
|
|
53
|
+
"is_empty",
|
|
54
|
+
"is_not_empty"
|
|
55
|
+
],
|
|
56
|
+
status: [
|
|
57
|
+
"eq",
|
|
58
|
+
"neq",
|
|
59
|
+
"in",
|
|
60
|
+
"is_empty",
|
|
61
|
+
"is_not_empty"
|
|
62
|
+
],
|
|
63
|
+
priority: [
|
|
64
|
+
"eq",
|
|
65
|
+
"neq",
|
|
66
|
+
"in",
|
|
67
|
+
"is_empty",
|
|
68
|
+
"is_not_empty"
|
|
69
|
+
],
|
|
70
|
+
"multi-select": [
|
|
71
|
+
"contains",
|
|
72
|
+
"not_contains",
|
|
73
|
+
"is_empty",
|
|
74
|
+
"is_not_empty"
|
|
75
|
+
],
|
|
76
|
+
int: [
|
|
77
|
+
"eq",
|
|
78
|
+
"neq",
|
|
79
|
+
"gt",
|
|
80
|
+
"lt",
|
|
81
|
+
"gte",
|
|
82
|
+
"lte",
|
|
83
|
+
"is_empty",
|
|
84
|
+
"is_not_empty"
|
|
85
|
+
],
|
|
86
|
+
money: [
|
|
87
|
+
"eq",
|
|
88
|
+
"neq",
|
|
89
|
+
"gt",
|
|
90
|
+
"lt",
|
|
91
|
+
"gte",
|
|
92
|
+
"lte",
|
|
93
|
+
"is_empty",
|
|
94
|
+
"is_not_empty"
|
|
95
|
+
],
|
|
96
|
+
person: [
|
|
97
|
+
"eq",
|
|
98
|
+
"neq",
|
|
99
|
+
"contains",
|
|
100
|
+
"not_contains",
|
|
101
|
+
"is_empty",
|
|
102
|
+
"is_not_empty"
|
|
103
|
+
],
|
|
104
|
+
date: [
|
|
105
|
+
"eq",
|
|
106
|
+
"neq",
|
|
107
|
+
"gt",
|
|
108
|
+
"lt",
|
|
109
|
+
"gte",
|
|
110
|
+
"lte",
|
|
111
|
+
"is_empty",
|
|
112
|
+
"is_not_empty"
|
|
113
|
+
],
|
|
114
|
+
kind: ["in"]
|
|
115
|
+
};
|
|
116
|
+
const operatorsFor = (valueType) => OPERATORS_BY_VALUE_TYPE[valueType];
|
|
117
|
+
const valueEditorFor = (valueType, operator) => {
|
|
118
|
+
if (operator === "is_empty" || operator === "is_not_empty") return "none";
|
|
119
|
+
if (valueType === "single-select" || valueType === "multi-select" || valueType === "status" || valueType === "priority" || valueType === "kind") return "select";
|
|
120
|
+
if (valueType === "int" || valueType === "money") return "int";
|
|
121
|
+
if (valueType === "date") return "date";
|
|
122
|
+
return "text";
|
|
123
|
+
};
|
|
124
|
+
/** Whether the value editor accepts multiple selections. */
|
|
125
|
+
const isMultiValue = (operator) => operator === "contains_all" || operator === "in";
|
|
126
|
+
/** Compares two ref operands for the field-matching needed by the row. */
|
|
127
|
+
const operandsEqual = (a, b) => {
|
|
128
|
+
if (a.type !== b.type) return false;
|
|
129
|
+
if (a.type === "property" && b.type === "property") return a.propertyId === b.propertyId;
|
|
130
|
+
if (a.type === "builtin" && b.type === "builtin") return a.field === b.field;
|
|
131
|
+
if (a.type === "path" && b.type === "path") return a.path === b.path;
|
|
132
|
+
if (a.type === "formula" && b.type === "formula") return a.expr === b.expr;
|
|
133
|
+
return true;
|
|
134
|
+
};
|
|
135
|
+
/** Reads the ref operand a leaf node filters on, or null for a group. */
|
|
136
|
+
const leafOperand = (node) => {
|
|
137
|
+
if (node.type === "compare" && node.left.type !== "literal") return node.left;
|
|
138
|
+
if (node.type === "predicate" && node.operand.type !== "literal") return node.operand;
|
|
139
|
+
return null;
|
|
140
|
+
};
|
|
141
|
+
/** Reads the surface operator a leaf node represents. */
|
|
142
|
+
const leafOperator = (node) => {
|
|
143
|
+
if (node.type === "compare") return compareToOperator(node.op);
|
|
144
|
+
if (node.type === "predicate") return predicateToOperator(node.op);
|
|
145
|
+
return null;
|
|
146
|
+
};
|
|
147
|
+
const compareToOperator = (op) => op;
|
|
148
|
+
const predicateToOperator = (op) => {
|
|
149
|
+
if (op === "contains" || op === "not_contains" || op === "starts_with" || op === "ends_with" || op === "contains_all" || op === "in" || op === "is_empty" || op === "is_not_empty") return op;
|
|
150
|
+
return null;
|
|
151
|
+
};
|
|
152
|
+
/** Reads a leaf's value as a string (scalar editors). */
|
|
153
|
+
const leafValueString = (node) => {
|
|
154
|
+
if (node.type === "compare" && node.right.type === "literal") {
|
|
155
|
+
const { value } = node.right;
|
|
156
|
+
if (Array.isArray(value)) return value.join(", ");
|
|
157
|
+
return String(value);
|
|
158
|
+
}
|
|
159
|
+
if (node.type === "predicate" && typeof node.value === "string") return node.value;
|
|
160
|
+
return "";
|
|
161
|
+
};
|
|
162
|
+
/** Reads a leaf's value as a string list (multi editors). */
|
|
163
|
+
const leafValueList = (node) => {
|
|
164
|
+
if (node.type === "predicate" && Array.isArray(node.value)) return node.value;
|
|
165
|
+
if (node.type === "predicate" && typeof node.value === "string") return node.value === "" ? [] : [node.value];
|
|
166
|
+
return [];
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Rebuilds a leaf node from its editor state. `compare` operators
|
|
170
|
+
* produce a literal right operand; predicate operators carry their
|
|
171
|
+
* payload (or none, for `is_empty`).
|
|
172
|
+
*/
|
|
173
|
+
const buildLeaf = ({ operand, operator, value }) => {
|
|
174
|
+
if (isCompareOperator(operator)) return {
|
|
175
|
+
type: "compare",
|
|
176
|
+
left: operand,
|
|
177
|
+
op: operator,
|
|
178
|
+
right: {
|
|
179
|
+
type: "literal",
|
|
180
|
+
value: Array.isArray(value) ? value.at(0) ?? "" : value
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
if (operator === "is_empty" || operator === "is_not_empty") return {
|
|
184
|
+
type: "predicate",
|
|
185
|
+
operand,
|
|
186
|
+
op: operator
|
|
187
|
+
};
|
|
188
|
+
if (operator === "contains" || operator === "not_contains" || operator === "starts_with" || operator === "ends_with") return {
|
|
189
|
+
type: "predicate",
|
|
190
|
+
operand,
|
|
191
|
+
op: operator,
|
|
192
|
+
value: Array.isArray(value) ? value.at(0) ?? "" : value
|
|
193
|
+
};
|
|
194
|
+
return {
|
|
195
|
+
type: "predicate",
|
|
196
|
+
operand,
|
|
197
|
+
op: operator,
|
|
198
|
+
value: toList(value)
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
const toList = (value) => {
|
|
202
|
+
if (Array.isArray(value)) return value;
|
|
203
|
+
return value === "" ? [] : [value];
|
|
204
|
+
};
|
|
205
|
+
/** A fresh leaf for a newly added row, with the field's first operator. */
|
|
206
|
+
const leafFromField = (field) => {
|
|
207
|
+
const operator = operatorsFor(field.valueType).at(0) ?? "eq";
|
|
208
|
+
const value = isMultiValue(operator) ? [] : "";
|
|
209
|
+
return buildLeaf({
|
|
210
|
+
operand: field.operand,
|
|
211
|
+
operator,
|
|
212
|
+
value
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
/** Finds the field a leaf node targets, or null when none matches. */
|
|
216
|
+
const fieldForNode = (node, fields) => {
|
|
217
|
+
const operand = leafOperand(node);
|
|
218
|
+
if (!operand) return null;
|
|
219
|
+
return fields.find((field) => operandsEqual(field.operand, operand)) ?? null;
|
|
220
|
+
};
|
|
221
|
+
/** Normalizes the controlled `value` prop into a concrete group root. */
|
|
222
|
+
const asGroup = (value) => {
|
|
223
|
+
if (value?.type === "group") return value;
|
|
224
|
+
if (value) return {
|
|
225
|
+
type: "group",
|
|
226
|
+
combinator: "and",
|
|
227
|
+
children: [value]
|
|
228
|
+
};
|
|
229
|
+
return {
|
|
230
|
+
type: "group",
|
|
231
|
+
combinator: "and",
|
|
232
|
+
children: []
|
|
233
|
+
};
|
|
234
|
+
};
|
|
235
|
+
const replaceChild = (group, index, child) => ({
|
|
236
|
+
...group,
|
|
237
|
+
children: group.children.map((existing, i) => i === index ? child : existing)
|
|
238
|
+
});
|
|
239
|
+
const removeChild = (group, index) => ({
|
|
240
|
+
...group,
|
|
241
|
+
children: group.children.filter((_, i) => i !== index)
|
|
242
|
+
});
|
|
243
|
+
const appendChild = (group, child) => ({
|
|
244
|
+
...group,
|
|
245
|
+
children: [...group.children, child]
|
|
246
|
+
});
|
|
247
|
+
//#endregion
|
|
248
|
+
export { CONDITION_OPERATORS, appendChild, asGroup, buildLeaf, fieldForNode, isConditionOperator, isMultiValue, leafFromField, leafOperand, leafOperator, leafValueList, leafValueString, operandsEqual, operatorsFor, removeChild, replaceChild, valueEditorFor };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { GenericProperty, WorkspaceFieldContent } from "./types.js";
|
|
2
|
+
//#region src/field-value.d.ts
|
|
3
|
+
type FieldValueVariant = "default" | "table" | "kanban";
|
|
4
|
+
type FieldValueProps = {
|
|
5
|
+
content: WorkspaceFieldContent | undefined;
|
|
6
|
+
property: GenericProperty;
|
|
7
|
+
pendingPreview?: string | null | undefined;
|
|
8
|
+
variant?: FieldValueVariant;
|
|
9
|
+
};
|
|
10
|
+
declare const FieldValue: ({ content, property, pendingPreview, variant }: FieldValueProps) => import("react").JSX.Element | null;
|
|
11
|
+
declare const IntFieldValue: ({ content, variant }: {
|
|
12
|
+
content: Extract<WorkspaceFieldContent, {
|
|
13
|
+
type: "int";
|
|
14
|
+
}>;
|
|
15
|
+
variant?: FieldValueVariant;
|
|
16
|
+
}) => import("react").JSX.Element;
|
|
17
|
+
declare const MoneyFieldValue: ({ content, variant }: {
|
|
18
|
+
content: Extract<WorkspaceFieldContent, {
|
|
19
|
+
type: "money";
|
|
20
|
+
}>;
|
|
21
|
+
variant?: FieldValueVariant;
|
|
22
|
+
}) => import("react").JSX.Element;
|
|
23
|
+
declare const PersonFieldValue: ({ content, variant }: {
|
|
24
|
+
content: Extract<WorkspaceFieldContent, {
|
|
25
|
+
type: "person";
|
|
26
|
+
}>;
|
|
27
|
+
variant?: FieldValueVariant;
|
|
28
|
+
}) => import("react").JSX.Element;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { FieldValue, IntFieldValue, MoneyFieldValue, PersonFieldValue };
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { formatMoneyCents } from "./calculation-format.js";
|
|
2
|
+
import { emptyColor, resolveOptionColor } from "./colors.js";
|
|
3
|
+
import { getClipFieldValueLabel } from "./field-value-logic.js";
|
|
4
|
+
import { Result } from "better-result";
|
|
5
|
+
import { Loader2Icon, SquareMinusIcon } from "lucide-react";
|
|
6
|
+
import { useFormatter, useLocale, useTranslations } from "use-intl";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { BidiText } from "@stll/ui/bidi-text";
|
|
9
|
+
import { Skeleton } from "@stll/ui/skeleton";
|
|
10
|
+
import { cn } from "@stll/ui/utils";
|
|
11
|
+
//#region src/field-value.tsx
|
|
12
|
+
const FieldValue = ({ content, property, pendingPreview, variant }) => {
|
|
13
|
+
const resolvedVariant = variant ?? "default";
|
|
14
|
+
if (!content) return resolvedVariant === "table" ? null : /* @__PURE__ */ jsx(EmptyFieldValue, { variant: resolvedVariant });
|
|
15
|
+
if (content.type === "pending") return /* @__PURE__ */ jsx(PendingFieldValue, {
|
|
16
|
+
contentType: property.content.type,
|
|
17
|
+
preview: pendingPreview,
|
|
18
|
+
variant: resolvedVariant
|
|
19
|
+
});
|
|
20
|
+
if (content.type === "error") return /* @__PURE__ */ jsx(ErrorFieldValue, { variant: resolvedVariant });
|
|
21
|
+
if (content.type === "unsupported") return /* @__PURE__ */ jsx(UnsupportedFieldValue, { variant: resolvedVariant });
|
|
22
|
+
if (content.type === "file") return /* @__PURE__ */ jsx(FileFieldValue, {
|
|
23
|
+
content,
|
|
24
|
+
variant: resolvedVariant
|
|
25
|
+
});
|
|
26
|
+
if (content.type === "text") return /* @__PURE__ */ jsx(TextFieldValue, {
|
|
27
|
+
content,
|
|
28
|
+
variant: resolvedVariant
|
|
29
|
+
});
|
|
30
|
+
if (content.type === "date") return /* @__PURE__ */ jsx(DateFieldValue, {
|
|
31
|
+
content,
|
|
32
|
+
property,
|
|
33
|
+
variant: resolvedVariant
|
|
34
|
+
});
|
|
35
|
+
if (content.type === "int") return /* @__PURE__ */ jsx(IntFieldValue, {
|
|
36
|
+
content,
|
|
37
|
+
variant: resolvedVariant
|
|
38
|
+
});
|
|
39
|
+
if (content.type === "money") return /* @__PURE__ */ jsx(MoneyFieldValue, {
|
|
40
|
+
content,
|
|
41
|
+
variant: resolvedVariant
|
|
42
|
+
});
|
|
43
|
+
if (content.type === "person") return /* @__PURE__ */ jsx(PersonFieldValue, {
|
|
44
|
+
content,
|
|
45
|
+
variant: resolvedVariant
|
|
46
|
+
});
|
|
47
|
+
if (content.type === "single-select") return /* @__PURE__ */ jsx(SelectFieldValue, {
|
|
48
|
+
property,
|
|
49
|
+
value: content.value,
|
|
50
|
+
variant: resolvedVariant
|
|
51
|
+
});
|
|
52
|
+
if (content.type === "multi-select") return /* @__PURE__ */ jsx(MultiSelectFieldValue, {
|
|
53
|
+
property,
|
|
54
|
+
value: content.value,
|
|
55
|
+
variant: resolvedVariant
|
|
56
|
+
});
|
|
57
|
+
return /* @__PURE__ */ jsx(ClipFieldValue, {
|
|
58
|
+
content,
|
|
59
|
+
variant: resolvedVariant
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
const IntFieldValue = ({ content, variant }) => {
|
|
63
|
+
const format = useFormatter();
|
|
64
|
+
const className = getIntClassName(variant ?? "default");
|
|
65
|
+
const fallback = `${format.number(content.value)} ${content.currency}`;
|
|
66
|
+
if (!content.currency) return /* @__PURE__ */ jsx("span", {
|
|
67
|
+
className,
|
|
68
|
+
children: format.number(content.value)
|
|
69
|
+
});
|
|
70
|
+
const formattedResult = Result.try(() => format.number(content.value, {
|
|
71
|
+
style: "currency",
|
|
72
|
+
currency: content.currency ?? void 0,
|
|
73
|
+
minimumFractionDigits: 0
|
|
74
|
+
}));
|
|
75
|
+
if (formattedResult.isErr()) return /* @__PURE__ */ jsx("span", {
|
|
76
|
+
className,
|
|
77
|
+
children: fallback
|
|
78
|
+
});
|
|
79
|
+
return /* @__PURE__ */ jsx("span", {
|
|
80
|
+
className,
|
|
81
|
+
children: formattedResult.value
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
const MoneyFieldValue = ({ content, variant }) => {
|
|
85
|
+
const locale = useLocale();
|
|
86
|
+
return /* @__PURE__ */ jsx("span", {
|
|
87
|
+
className: cn(getIntClassName(variant ?? "default")),
|
|
88
|
+
children: formatMoneyCents({
|
|
89
|
+
amountCents: content.amountCents,
|
|
90
|
+
currency: content.currency,
|
|
91
|
+
locale
|
|
92
|
+
})
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
const PersonFieldValue = ({ content, variant }) => {
|
|
96
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
97
|
+
className: cn("flex max-w-full min-w-0 items-center truncate", (variant ?? "default") === "kanban" ? "text-muted-foreground bg-muted/60 gap-1 rounded px-1.5 py-0.5 text-xs leading-none" : "gap-1.5 text-sm"),
|
|
98
|
+
children: [/* @__PURE__ */ jsx(PersonAvatar, {
|
|
99
|
+
image: content.image,
|
|
100
|
+
name: content.name
|
|
101
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
102
|
+
className: "truncate",
|
|
103
|
+
children: content.name
|
|
104
|
+
})]
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* The person's picture, or their initial when there is none. Deliberately not
|
|
109
|
+
* the app's user avatar: a person field names someone who may not be a
|
|
110
|
+
* workspace member at all, so there is no account to render.
|
|
111
|
+
*/
|
|
112
|
+
const PersonAvatar = ({ image, name }) => {
|
|
113
|
+
if (image) return /* @__PURE__ */ jsx("img", {
|
|
114
|
+
alt: "",
|
|
115
|
+
className: "size-4 shrink-0 rounded-full object-cover",
|
|
116
|
+
src: image
|
|
117
|
+
});
|
|
118
|
+
return /* @__PURE__ */ jsx("span", {
|
|
119
|
+
"aria-hidden": true,
|
|
120
|
+
className: "bg-muted text-muted-foreground flex size-4 shrink-0 items-center justify-center rounded-full text-[9px] uppercase",
|
|
121
|
+
children: firstGrapheme(name)
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* The first character of a name, as a reader sees it: a spread over a string
|
|
126
|
+
* yields code points, which splits an emoji or a combining mark in half.
|
|
127
|
+
*/
|
|
128
|
+
const firstGrapheme = (value) => {
|
|
129
|
+
return [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(value)].at(0)?.segment ?? "?";
|
|
130
|
+
};
|
|
131
|
+
const EmptyFieldValue = ({ variant }) => {
|
|
132
|
+
if (variant === "kanban") return null;
|
|
133
|
+
return /* @__PURE__ */ jsx("span", {
|
|
134
|
+
className: "text-muted-foreground text-sm",
|
|
135
|
+
children: "—"
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
const PendingFieldValue = ({ contentType, preview, variant }) => {
|
|
139
|
+
const t = useTranslations();
|
|
140
|
+
const trimmedPreview = preview?.trim();
|
|
141
|
+
const hasPreview = trimmedPreview !== void 0 && trimmedPreview.length > 0;
|
|
142
|
+
if (variant === "kanban") return null;
|
|
143
|
+
if (variant === "table") return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Loader2Icon, {
|
|
144
|
+
"aria-hidden": "true",
|
|
145
|
+
className: "text-muted-foreground absolute end-1 top-1 z-20 size-3 shrink-0 animate-spin",
|
|
146
|
+
strokeWidth: 2.25
|
|
147
|
+
}), hasPreview ? /* @__PURE__ */ jsx(BidiText, {
|
|
148
|
+
as: "div",
|
|
149
|
+
className: "line-clamp-2 min-w-0",
|
|
150
|
+
children: trimmedPreview
|
|
151
|
+
}) : /* @__PURE__ */ jsx(PendingSkeleton, { contentType })] });
|
|
152
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
153
|
+
className: "text-muted-foreground flex items-center gap-1.5 text-sm",
|
|
154
|
+
children: [t("workspaces.fields.calculating"), /* @__PURE__ */ jsx("span", { className: "bg-muted-foreground size-2 animate-pulse rounded-full" })]
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
const ErrorFieldValue = ({ variant }) => {
|
|
158
|
+
const t = useTranslations();
|
|
159
|
+
if (variant === "kanban") return null;
|
|
160
|
+
return /* @__PURE__ */ jsx("span", {
|
|
161
|
+
className: "text-destructive line-clamp-2 text-sm italic",
|
|
162
|
+
children: t("workspaces.fields.errored")
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
const UnsupportedFieldValue = ({ variant }) => {
|
|
166
|
+
const t = useTranslations();
|
|
167
|
+
if (variant === "kanban") return null;
|
|
168
|
+
return /* @__PURE__ */ jsx("span", {
|
|
169
|
+
className: "text-muted-foreground line-clamp-2 text-sm italic",
|
|
170
|
+
children: t("workspaces.fields.formatNotSupported")
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
const FileFieldValue = ({ content, variant }) => {
|
|
174
|
+
if (variant === "kanban") return null;
|
|
175
|
+
return /* @__PURE__ */ jsx(BidiText, {
|
|
176
|
+
as: "span",
|
|
177
|
+
className: variant === "table" ? "truncate" : "text-sm",
|
|
178
|
+
children: content.fileName
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
const TextFieldValue = ({ content, variant }) => {
|
|
182
|
+
if (variant === "kanban") {
|
|
183
|
+
if (!content.value.trim()) return null;
|
|
184
|
+
return /* @__PURE__ */ jsx(BidiText, {
|
|
185
|
+
as: "span",
|
|
186
|
+
className: "text-muted-foreground line-clamp-2 min-w-0 basis-full text-xs leading-4",
|
|
187
|
+
children: content.value
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return /* @__PURE__ */ jsx(BidiText, {
|
|
191
|
+
as: "span",
|
|
192
|
+
className: variant === "table" ? "line-clamp-2" : "line-clamp-2 text-sm",
|
|
193
|
+
children: content.value
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
const DateFieldValue = ({ content, property, variant }) => {
|
|
197
|
+
const format = useFormatter();
|
|
198
|
+
const date = content.value ? new Date(content.value) : null;
|
|
199
|
+
if (!date || Number.isNaN(date.getTime())) {
|
|
200
|
+
if (variant === "table") return /* @__PURE__ */ jsx(SelectFieldValue, {
|
|
201
|
+
property,
|
|
202
|
+
value: null,
|
|
203
|
+
variant
|
|
204
|
+
});
|
|
205
|
+
return /* @__PURE__ */ jsx(EmptyFieldValue, { variant });
|
|
206
|
+
}
|
|
207
|
+
const formatted = format.dateTime(date, {
|
|
208
|
+
year: "numeric",
|
|
209
|
+
month: "short",
|
|
210
|
+
day: "numeric",
|
|
211
|
+
timeZone: "UTC"
|
|
212
|
+
});
|
|
213
|
+
if (variant === "kanban") return /* @__PURE__ */ jsx("span", {
|
|
214
|
+
className: "text-muted-foreground bg-muted/60 rounded px-1.5 py-0.5 text-xs leading-none",
|
|
215
|
+
children: formatted
|
|
216
|
+
});
|
|
217
|
+
return /* @__PURE__ */ jsx("span", {
|
|
218
|
+
className: variant === "default" ? "text-sm" : void 0,
|
|
219
|
+
children: formatted
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
const SelectFieldValue = ({ property, value, variant }) => {
|
|
223
|
+
const t = useTranslations();
|
|
224
|
+
const color = getSelectPropertyColor(property, value);
|
|
225
|
+
if (variant === "kanban") return /* @__PURE__ */ jsx(BidiText, {
|
|
226
|
+
as: "span",
|
|
227
|
+
className: "max-w-full truncate rounded px-1.5 py-0.5 text-xs leading-none font-medium",
|
|
228
|
+
style: {
|
|
229
|
+
backgroundColor: color?.background,
|
|
230
|
+
color: color?.foreground
|
|
231
|
+
},
|
|
232
|
+
children: value ?? t("common.empty")
|
|
233
|
+
});
|
|
234
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
235
|
+
className: variant === "table" ? "flex max-w-full items-center gap-x-1 rounded px-1 py-0.25 font-medium" : "flex w-max max-w-full items-center gap-x-1 rounded px-1 py-0.25 text-sm font-medium",
|
|
236
|
+
style: {
|
|
237
|
+
backgroundColor: color?.background,
|
|
238
|
+
color: color?.foreground
|
|
239
|
+
},
|
|
240
|
+
children: [variant === "table" && !value && /* @__PURE__ */ jsx(SquareMinusIcon, { className: "size-4" }), /* @__PURE__ */ jsx(BidiText, {
|
|
241
|
+
as: "span",
|
|
242
|
+
className: "truncate",
|
|
243
|
+
children: value ?? t("common.empty")
|
|
244
|
+
})]
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
const MultiSelectFieldValue = ({ property, value, variant }) => {
|
|
248
|
+
if (value.length === 0) {
|
|
249
|
+
if (variant === "table") return /* @__PURE__ */ jsx(SelectFieldValue, {
|
|
250
|
+
property,
|
|
251
|
+
value: null,
|
|
252
|
+
variant
|
|
253
|
+
});
|
|
254
|
+
return /* @__PURE__ */ jsx(EmptyFieldValue, { variant });
|
|
255
|
+
}
|
|
256
|
+
return /* @__PURE__ */ jsx("span", {
|
|
257
|
+
className: variant === "table" ? "flex min-w-0 flex-wrap gap-1.5" : "flex flex-wrap gap-1",
|
|
258
|
+
children: value.map((option) => /* @__PURE__ */ jsx(SelectFieldValue, {
|
|
259
|
+
property,
|
|
260
|
+
value: option,
|
|
261
|
+
variant
|
|
262
|
+
}, option))
|
|
263
|
+
});
|
|
264
|
+
};
|
|
265
|
+
const ClipFieldValue = ({ content, variant }) => {
|
|
266
|
+
const value = getClipFieldValueLabel({
|
|
267
|
+
citation: content.citation,
|
|
268
|
+
url: content.url
|
|
269
|
+
});
|
|
270
|
+
if (variant === "kanban") return /* @__PURE__ */ jsx(BidiText, {
|
|
271
|
+
as: "span",
|
|
272
|
+
className: "text-muted-foreground bg-muted/60 truncate rounded px-1.5 py-0.5 text-xs leading-none",
|
|
273
|
+
children: value
|
|
274
|
+
});
|
|
275
|
+
return /* @__PURE__ */ jsx(BidiText, {
|
|
276
|
+
as: "span",
|
|
277
|
+
className: "text-muted-foreground block truncate text-sm",
|
|
278
|
+
children: value
|
|
279
|
+
});
|
|
280
|
+
};
|
|
281
|
+
const PendingSkeleton = ({ contentType }) => {
|
|
282
|
+
if (contentType === "single-select") return /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-16 rounded-full" });
|
|
283
|
+
if (contentType === "multi-select") return /* @__PURE__ */ jsxs("div", {
|
|
284
|
+
className: "flex flex-wrap gap-1",
|
|
285
|
+
children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-12 rounded-full" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-16 rounded-full" })]
|
|
286
|
+
});
|
|
287
|
+
if (contentType === "date") return /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-20" });
|
|
288
|
+
if (contentType === "int") return /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-10" });
|
|
289
|
+
if (contentType === "file") return /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-24" });
|
|
290
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
291
|
+
className: "flex w-full max-w-[12rem] flex-col gap-1",
|
|
292
|
+
children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-full" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-3/4" })]
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
const getSelectPropertyColor = (property, option) => {
|
|
296
|
+
if (!option) return emptyColor;
|
|
297
|
+
if (property.content.type !== "single-select" && property.content.type !== "multi-select") return;
|
|
298
|
+
const color = property.content.options.find((o) => o.value === option)?.color;
|
|
299
|
+
if (color === void 0) return;
|
|
300
|
+
return resolveOptionColor(color);
|
|
301
|
+
};
|
|
302
|
+
const getIntClassName = (variant) => {
|
|
303
|
+
if (variant === "kanban") return "text-muted-foreground bg-muted/60 rounded px-1.5 py-0.5 text-xs leading-none tabular-nums";
|
|
304
|
+
if (variant === "table") return "block max-w-full min-w-0 truncate text-start tabular-nums";
|
|
305
|
+
return "block min-w-0 max-w-full truncate text-start text-sm tabular-nums";
|
|
306
|
+
};
|
|
307
|
+
//#endregion
|
|
308
|
+
export { FieldValue, IntFieldValue, MoneyFieldValue, PersonFieldValue };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CalculationFormatters, CalculationLabels, FormatCalculationParams, FormatMoneyCentsParams, FormattedCalculation, currencyMinorUnitDigits, formatCalculationResult, formatMoneyCents } from "./calculation-format.js";
|
|
2
|
+
import { ApplyCalculationSelectionParams, CalculationSelection, applyCalculationSelection } from "./calculation-selection.js";
|
|
3
|
+
import { CalculationKindPicker, CalculationKindPickerProps, CalculationPicker, CalculationPickerProps, CalculationProperty, CalculationResult, CalculationSummary, CalculationSummaryProps, ColumnCalculation, UseCalculationParams, WorkspaceCalculationLabels, useCalculation } from "./calculations.js";
|
|
4
|
+
import { ColorVariants, OptionColor, emptyColor, optionColors, resolveOptionColor } from "./colors.js";
|
|
5
|
+
import { FIELD_CONTENT_TYPES, FieldContent, GenericProperty, WorkspaceFieldContent } from "./types.js";
|
|
6
|
+
import { PropertyIcon, PropertyIconType } from "./property-icon.js";
|
|
7
|
+
import { CONDITION_OPERATORS, ConditionOperator, FieldOption, FieldOptionChoice, FieldValueType, ValueEditorKind, appendChild, asGroup, buildLeaf, fieldForNode, isConditionOperator, isMultiValue, leafFromField, leafOperand, leafOperator, leafValueList, leafValueString, operandsEqual, operatorsFor, removeChild, replaceChild, valueEditorFor } from "./conditions/logic.js";
|
|
8
|
+
import { ConditionBuilder, ConditionBuilderLabels, ConditionCapabilities, FormulaCellRenderCtx, ValueEditorRenderCtx } from "./conditions/builder.js";
|
|
9
|
+
import { getClipFieldValueLabel } from "./field-value-logic.js";
|
|
10
|
+
import { FieldValue, IntFieldValue, MoneyFieldValue, PersonFieldValue } from "./field-value.js";
|
|
11
|
+
import { SortChips, SortChipsLabels, SortChipsProps, SortDescriptor, SortableProperty, sortDirectionHint } from "./sorts.js";
|
|
12
|
+
import { TableSkeletonRows } from "./table-skeleton-rows.js";
|
|
13
|
+
import { WorkspaceViewDirection, WorkspaceViewDropPosition, reorderWorkspaceViewIds, toWorkspaceViewDropPosition } from "./view-switcher.logic.js";
|
|
14
|
+
import { WorkspaceViewSwitcher, WorkspaceViewSwitcherEditing, WorkspaceViewSwitcherItem, WorkspaceViewSwitcherProps, WorkspaceViewSwitcherReorder } from "./view-switcher.js";
|
|
15
|
+
export { type ApplyCalculationSelectionParams, CONDITION_OPERATORS, type CalculationFormatters, CalculationKindPicker, type CalculationKindPickerProps, type CalculationLabels, CalculationPicker, type CalculationPickerProps, type CalculationProperty, type CalculationResult, type CalculationSelection, CalculationSummary, type CalculationSummaryProps, type ColorVariants, ColumnCalculation, ConditionBuilder, type ConditionBuilderLabels, type ConditionCapabilities, type ConditionOperator, FIELD_CONTENT_TYPES, type FieldContent, type FieldOption, type FieldOptionChoice, FieldValue, type FieldValueType, type FormatCalculationParams, type FormatMoneyCentsParams, type FormattedCalculation, type FormulaCellRenderCtx, type GenericProperty, IntFieldValue, MoneyFieldValue, type OptionColor, PersonFieldValue, PropertyIcon, type PropertyIconType, SortChips, type SortChipsLabels, type SortChipsProps, type SortDescriptor, type SortableProperty, TableSkeletonRows, type UseCalculationParams, type ValueEditorKind, type ValueEditorRenderCtx, type WorkspaceCalculationLabels, type WorkspaceFieldContent, type WorkspaceViewDirection, type WorkspaceViewDropPosition, WorkspaceViewSwitcher, type WorkspaceViewSwitcherEditing, type WorkspaceViewSwitcherItem, type WorkspaceViewSwitcherProps, type WorkspaceViewSwitcherReorder, appendChild, applyCalculationSelection, asGroup, buildLeaf, currencyMinorUnitDigits, emptyColor, fieldForNode, formatCalculationResult, formatMoneyCents, getClipFieldValueLabel, isConditionOperator, isMultiValue, leafFromField, leafOperand, leafOperator, leafValueList, leafValueString, operandsEqual, operatorsFor, optionColors, removeChild, reorderWorkspaceViewIds, replaceChild, resolveOptionColor, sortDirectionHint, toWorkspaceViewDropPosition, useCalculation, valueEditorFor };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { currencyMinorUnitDigits, formatCalculationResult, formatMoneyCents } from "./calculation-format.js";
|
|
2
|
+
import { applyCalculationSelection } from "./calculation-selection.js";
|
|
3
|
+
import { CalculationKindPicker, CalculationPicker, CalculationSummary, ColumnCalculation, useCalculation } from "./calculations.js";
|
|
4
|
+
import { emptyColor, optionColors, resolveOptionColor } from "./colors.js";
|
|
5
|
+
import { getClipFieldValueLabel } from "./field-value-logic.js";
|
|
6
|
+
import { FieldValue, IntFieldValue, MoneyFieldValue, PersonFieldValue } from "./field-value.js";
|
|
7
|
+
import { CONDITION_OPERATORS, appendChild, asGroup, buildLeaf, fieldForNode, isConditionOperator, isMultiValue, leafFromField, leafOperand, leafOperator, leafValueList, leafValueString, operandsEqual, operatorsFor, removeChild, replaceChild, valueEditorFor } from "./conditions/logic.js";
|
|
8
|
+
import { ConditionBuilder } from "./conditions/builder.js";
|
|
9
|
+
import { PropertyIcon } from "./property-icon.js";
|
|
10
|
+
import { SortChips, sortDirectionHint } from "./sorts.js";
|
|
11
|
+
import { TableSkeletonRows } from "./table-skeleton-rows.js";
|
|
12
|
+
import { FIELD_CONTENT_TYPES } from "./types.js";
|
|
13
|
+
import { reorderWorkspaceViewIds, toWorkspaceViewDropPosition } from "./view-switcher.logic.js";
|
|
14
|
+
import { WorkspaceViewSwitcher } from "./view-switcher.js";
|
|
15
|
+
export { CONDITION_OPERATORS, CalculationKindPicker, CalculationPicker, CalculationSummary, ColumnCalculation, ConditionBuilder, FIELD_CONTENT_TYPES, FieldValue, IntFieldValue, MoneyFieldValue, PersonFieldValue, PropertyIcon, SortChips, TableSkeletonRows, WorkspaceViewSwitcher, appendChild, applyCalculationSelection, asGroup, buildLeaf, currencyMinorUnitDigits, emptyColor, fieldForNode, formatCalculationResult, formatMoneyCents, getClipFieldValueLabel, isConditionOperator, isMultiValue, leafFromField, leafOperand, leafOperator, leafValueList, leafValueString, operandsEqual, operatorsFor, optionColors, removeChild, reorderWorkspaceViewIds, replaceChild, resolveOptionColor, sortDirectionHint, toWorkspaceViewDropPosition, useCalculation, valueEditorFor };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FieldContent } from "./types.js";
|
|
2
|
+
//#region src/property-icon.d.ts
|
|
3
|
+
type FieldTypeWithoutPending = Exclude<FieldContent["type"], "pending">;
|
|
4
|
+
type PropertyContentType = "file" | "text" | "single-select" | "multi-select" | "date" | "int" | "money" | "person";
|
|
5
|
+
type PropertyIconType = FieldTypeWithoutPending | PropertyContentType;
|
|
6
|
+
type PropertyHelperProps = {
|
|
7
|
+
type: PropertyIconType;
|
|
8
|
+
className?: string;
|
|
9
|
+
};
|
|
10
|
+
declare const PropertyIcon: ({ type, className }: PropertyHelperProps) => import("react").JSX.Element;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { PropertyIcon, PropertyIconType };
|