@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.
@@ -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,7 @@
1
+ //#region src/field-value-logic.d.ts
2
+ declare const getClipFieldValueLabel: ({ citation, url }: {
3
+ citation: string | null | undefined;
4
+ url: string;
5
+ }) => string;
6
+ //#endregion
7
+ export { getClipFieldValueLabel };
@@ -0,0 +1,8 @@
1
+ //#region src/field-value-logic.ts
2
+ const getClipFieldValueLabel = ({ citation, url }) => {
3
+ const trimmedCitation = citation?.trim();
4
+ if (trimmedCitation) return trimmedCitation;
5
+ return url;
6
+ };
7
+ //#endregion
8
+ export { getClipFieldValueLabel };
@@ -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 };
@@ -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 };