@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,56 @@
1
+ import { AlertCircleIcon, BanknoteIcon, CalendarIcon, CircleDotIcon, FileIcon, FileQuestionIcon, HashIcon, LinkIcon, ListChecksIcon, TextIcon, UserIcon } from "lucide-react";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { cn } from "@stll/ui/utils";
4
+ //#region src/property-icon.tsx
5
+ const propertyMap = {
6
+ text: {
7
+ icon: TextIcon,
8
+ label: "Text"
9
+ },
10
+ money: {
11
+ icon: BanknoteIcon,
12
+ label: "Money"
13
+ },
14
+ person: {
15
+ icon: UserIcon,
16
+ label: "Person"
17
+ },
18
+ file: {
19
+ icon: FileIcon,
20
+ label: "File"
21
+ },
22
+ error: {
23
+ icon: AlertCircleIcon,
24
+ label: "Error"
25
+ },
26
+ "single-select": {
27
+ icon: CircleDotIcon,
28
+ label: "Single Select"
29
+ },
30
+ "multi-select": {
31
+ icon: ListChecksIcon,
32
+ label: "Multi Select"
33
+ },
34
+ unsupported: {
35
+ icon: FileQuestionIcon,
36
+ label: "Unsupported"
37
+ },
38
+ date: {
39
+ icon: CalendarIcon,
40
+ label: "Date"
41
+ },
42
+ int: {
43
+ icon: HashIcon,
44
+ label: "Number"
45
+ },
46
+ clip: {
47
+ icon: LinkIcon,
48
+ label: "Clip"
49
+ }
50
+ };
51
+ const PropertyIcon = ({ type, className }) => {
52
+ const Icon = propertyMap[type].icon;
53
+ return /* @__PURE__ */ jsx(Icon, { className: cn("size-3.5 shrink-0", className) });
54
+ };
55
+ //#endregion
56
+ export { PropertyIcon };
@@ -0,0 +1,41 @@
1
+ import { PropertyIconType } from "./property-icon.js";
2
+ //#region src/sorts.d.ts
3
+ /** One sort a view carries. */
4
+ type SortDescriptor = {
5
+ propertyId: string;
6
+ desc: boolean;
7
+ };
8
+ /** A property a view may sort by. */
9
+ type SortableProperty = {
10
+ id: string;
11
+ name: string;
12
+ type: PropertyIconType;
13
+ };
14
+ type SortChipsLabels = {
15
+ /** Accessible name of the "add a sort" trigger. */
16
+ add: string;
17
+ /** Accessible name of a chip's remove button. */
18
+ remove: string;
19
+ };
20
+ type SortChipsProps = {
21
+ sorts: readonly SortDescriptor[];
22
+ properties: readonly SortableProperty[];
23
+ onUpdate: (sorts: SortDescriptor[]) => void;
24
+ labels: SortChipsLabels;
25
+ };
26
+ /**
27
+ * The view's sorts as chips, plus the menu that adds one.
28
+ *
29
+ * A sort is a property id and a direction; which properties can be sorted is
30
+ * the caller's answer, so this reads nothing but the three fields a chip
31
+ * draws.
32
+ */
33
+ declare const SortChips: ({ sorts, properties, onUpdate, labels }: SortChipsProps) => import("react").JSX.Element;
34
+ /**
35
+ * What ascending means depends on what is being sorted: A→Z for words, 1→9 for
36
+ * numbers, an arrow for dates. A type with no idiom falls back to the arrow
37
+ * icon, which is why this returns null rather than a default string.
38
+ */
39
+ declare const sortDirectionHint: (propertyType: string | undefined, desc: boolean) => string | null;
40
+ //#endregion
41
+ export { SortChips, SortChipsLabels, SortChipsProps, SortDescriptor, SortableProperty, sortDirectionHint };
package/dist/sorts.js ADDED
@@ -0,0 +1,99 @@
1
+ import { PropertyIcon } from "./property-icon.js";
2
+ import { ArrowDownIcon, ArrowUpDownIcon, ArrowUpIcon, XIcon } from "lucide-react";
3
+ import { Button } from "@stll/ui/button";
4
+ import { Menu, MenuItem, MenuPopup, MenuTrigger } from "@stll/ui/menu";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/sorts.tsx
7
+ /**
8
+ * The view's sorts as chips, plus the menu that adds one.
9
+ *
10
+ * A sort is a property id and a direction; which properties can be sorted is
11
+ * the caller's answer, so this reads nothing but the three fields a chip
12
+ * draws.
13
+ */
14
+ const SortChips = ({ sorts, properties, onUpdate, labels }) => /* @__PURE__ */ jsxs(Fragment, { children: [sorts.map((sort) => {
15
+ const property = properties.find((candidate) => candidate.id === sort.propertyId);
16
+ if (!property) return null;
17
+ return /* @__PURE__ */ jsx(SortChip, {
18
+ desc: sort.desc,
19
+ labels,
20
+ onRemove: () => {
21
+ onUpdate(sorts.filter((candidate) => candidate.propertyId !== sort.propertyId));
22
+ },
23
+ onToggle: () => {
24
+ onUpdate(sorts.map((candidate) => candidate.propertyId === sort.propertyId ? {
25
+ ...candidate,
26
+ desc: !candidate.desc
27
+ } : candidate));
28
+ },
29
+ propertyName: property.name,
30
+ propertyType: property.type
31
+ }, sort.propertyId);
32
+ }), /* @__PURE__ */ jsx(AddSortButton, {
33
+ labels,
34
+ onAdd: (sort) => onUpdate([...sorts, sort]),
35
+ properties,
36
+ sortedPropertyIds: new Set(sorts.map((sort) => sort.propertyId))
37
+ })] });
38
+ /**
39
+ * What ascending means depends on what is being sorted: A→Z for words, 1→9 for
40
+ * numbers, an arrow for dates. A type with no idiom falls back to the arrow
41
+ * icon, which is why this returns null rather than a default string.
42
+ */
43
+ const sortDirectionHint = (propertyType, desc) => {
44
+ if (!propertyType) return null;
45
+ switch (propertyType) {
46
+ case "text":
47
+ case "single-select":
48
+ case "multi-select":
49
+ case "file":
50
+ case "person": return desc ? "Z→A" : "A→Z";
51
+ case "int":
52
+ case "money": return desc ? "9→1" : "1→9";
53
+ case "date": return desc ? "↓" : "↑";
54
+ default: return null;
55
+ }
56
+ };
57
+ const SortChip = ({ propertyName, propertyType, desc, onToggle, onRemove, labels }) => {
58
+ const SortIcon = desc ? ArrowDownIcon : ArrowUpIcon;
59
+ const hint = sortDirectionHint(propertyType, desc);
60
+ return /* @__PURE__ */ jsxs("div", {
61
+ className: "bg-muted/50 flex items-center rounded-md border",
62
+ children: [/* @__PURE__ */ jsxs(Button, {
63
+ onClick: onToggle,
64
+ size: "xs",
65
+ variant: "ghost",
66
+ children: [
67
+ !hint && /* @__PURE__ */ jsx(SortIcon, {}),
68
+ propertyName,
69
+ hint && /* @__PURE__ */ jsx("span", {
70
+ className: "text-muted-foreground",
71
+ children: hint
72
+ })
73
+ ]
74
+ }), /* @__PURE__ */ jsx(Button, {
75
+ "aria-label": labels.remove,
76
+ onClick: onRemove,
77
+ size: "icon-xs",
78
+ variant: "ghost",
79
+ children: /* @__PURE__ */ jsx(XIcon, {})
80
+ })]
81
+ });
82
+ };
83
+ const AddSortButton = ({ properties, sortedPropertyIds, onAdd, labels }) => /* @__PURE__ */ jsxs(Menu, { children: [/* @__PURE__ */ jsx(MenuTrigger, {
84
+ "aria-label": labels.add,
85
+ render: /* @__PURE__ */ jsx(Button, {
86
+ size: "icon-xs",
87
+ variant: "ghost"
88
+ }),
89
+ children: /* @__PURE__ */ jsx(ArrowUpDownIcon, {})
90
+ }), /* @__PURE__ */ jsx(MenuPopup, { children: properties.map((property) => /* @__PURE__ */ jsxs(MenuItem, {
91
+ disabled: sortedPropertyIds.has(property.id),
92
+ onClick: () => onAdd({
93
+ propertyId: property.id,
94
+ desc: false
95
+ }),
96
+ children: [/* @__PURE__ */ jsx(PropertyIcon, { type: property.type }), property.name]
97
+ }, property.id)) })] });
98
+ //#endregion
99
+ export { SortChips, sortDirectionHint };
@@ -0,0 +1,18 @@
1
+ import { ReactNode } from "react";
2
+ import { Column, RowData, TableFeatures } from "@tanstack/react-table";
3
+ //#region src/table-skeleton-rows.d.ts
4
+ type TableSkeletonRowsProps<TFeatures extends TableFeatures, TData extends RowData> = {
5
+ columns: readonly Column<TFeatures, TData>[];
6
+ rowCount?: number;
7
+ renderCell?: (column: Column<TFeatures, TData>) => ReactNode;
8
+ };
9
+ /**
10
+ * Loading rows generated from the table's own column model, so the skeleton
11
+ * cannot drift from the real table: add, remove, or reorder a column and the
12
+ * placeholder gains, loses, or moves the matching cell automatically. Render it
13
+ * inside the same `<TableBody>` the real rows use, passing the table's leaf
14
+ * columns (e.g. `table.getAllLeafColumns()`).
15
+ */
16
+ declare const TableSkeletonRows: <TFeatures extends TableFeatures, TData extends RowData>({ columns, rowCount, renderCell }: TableSkeletonRowsProps<TFeatures, TData>) => import("react").JSX.Element[];
17
+ //#endregion
18
+ export { TableSkeletonRows };
@@ -0,0 +1,34 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { Skeleton } from "@stll/ui/skeleton";
3
+ import { TableCell, TableRow } from "@stll/ui/table";
4
+ //#region src/table-skeleton-rows.tsx
5
+ const SKELETON_ROW_KEYS = [
6
+ "a",
7
+ "b",
8
+ "c",
9
+ "d",
10
+ "e",
11
+ "f",
12
+ "g",
13
+ "h",
14
+ "i",
15
+ "j",
16
+ "k",
17
+ "l"
18
+ ];
19
+ const DEFAULT_SKELETON_ROW_COUNT = 8;
20
+ /**
21
+ * Loading rows generated from the table's own column model, so the skeleton
22
+ * cannot drift from the real table: add, remove, or reorder a column and the
23
+ * placeholder gains, loses, or moves the matching cell automatically. Render it
24
+ * inside the same `<TableBody>` the real rows use, passing the table's leaf
25
+ * columns (e.g. `table.getAllLeafColumns()`).
26
+ */
27
+ const TableSkeletonRows = ({ columns, rowCount = DEFAULT_SKELETON_ROW_COUNT, renderCell }) => {
28
+ return SKELETON_ROW_KEYS.slice(0, Math.min(rowCount, SKELETON_ROW_KEYS.length)).map((rowKey) => /* @__PURE__ */ jsx(TableRow, { children: columns.map((column) => {
29
+ const custom = renderCell?.(column);
30
+ return /* @__PURE__ */ jsx(TableCell, { children: custom === void 0 ? /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-3/5" }) : custom }, column.id);
31
+ }) }, rowKey));
32
+ };
33
+ //#endregion
34
+ export { TableSkeletonRows };
@@ -0,0 +1,89 @@
1
+ import { OptionColor, OptionColor as OptionColor$1 } from "@stll/ui/option-color";
2
+ //#region src/types.d.ts
3
+ type FieldContent = {
4
+ type: "error";
5
+ version: 1;
6
+ } | {
7
+ type: "pending";
8
+ version: 1;
9
+ } | {
10
+ type: "unsupported";
11
+ version: 1;
12
+ } | {
13
+ type: "text";
14
+ version: 1;
15
+ value: string;
16
+ } | {
17
+ type: "single-select";
18
+ version: 1;
19
+ value: string | null;
20
+ } | {
21
+ type: "multi-select";
22
+ version: 1;
23
+ value: string[];
24
+ } | {
25
+ type: "file";
26
+ version: 1;
27
+ id: string;
28
+ fileName: string;
29
+ mimeType: string;
30
+ sizeBytes: number;
31
+ encrypted: boolean;
32
+ sha256Hex: string;
33
+ pdfFileId: string | null;
34
+ } | {
35
+ type: "date";
36
+ version: 1;
37
+ value: string | null;
38
+ } | {
39
+ type: "int";
40
+ version: 1;
41
+ value: number;
42
+ currency: string | null;
43
+ } | {
44
+ /**
45
+ * A monetary amount. Separate from `int` because it is stored in minor
46
+ * units and carries its own currency: an int's value is major units, so
47
+ * one type for both is a 100x bug waiting in every total.
48
+ */
49
+ type: "money";
50
+ version: 1;
51
+ amountCents: number;
52
+ currency: string;
53
+ } | {
54
+ type: "person";
55
+ version: 1;
56
+ /** Null when the person is named but not a workspace member. */
57
+ userId: string | null;
58
+ name: string;
59
+ image: string | null;
60
+ } | {
61
+ type: "clip";
62
+ version: 1;
63
+ url: string;
64
+ snippet?: string;
65
+ citation?: string;
66
+ jurisdiction?: string;
67
+ sourceType?: string;
68
+ };
69
+ /** Runtime names for every content arm the field renderer understands. */
70
+ declare const FIELD_CONTENT_TYPES: readonly ["error", "pending", "unsupported", "text", "single-select", "multi-select", "file", "date", "int", "money", "person", "clip"];
71
+ type WorkspaceFieldContent = FieldContent;
72
+ /**
73
+ * Minimal structural bound the display layer needs from a property: the cell
74
+ * renderers only read `content.type` and, for selects, the `options` colors.
75
+ * Real callers (e.g. the app's `WorkspaceProperty`) satisfy this structurally.
76
+ */
77
+ type GenericProperty = {
78
+ content: {
79
+ type: "file" | "text" | "date" | "int" | "money" | "person";
80
+ } | {
81
+ type: "single-select" | "multi-select";
82
+ options: {
83
+ value: string;
84
+ color: OptionColor$1;
85
+ }[];
86
+ };
87
+ };
88
+ //#endregion
89
+ export { FIELD_CONTENT_TYPES, FieldContent, GenericProperty, type OptionColor, WorkspaceFieldContent };
package/dist/types.js ADDED
@@ -0,0 +1,17 @@
1
+ /** Runtime names for every content arm the field renderer understands. */
2
+ const FIELD_CONTENT_TYPES = [
3
+ "error",
4
+ "pending",
5
+ "unsupported",
6
+ "text",
7
+ "single-select",
8
+ "multi-select",
9
+ "file",
10
+ "date",
11
+ "int",
12
+ "money",
13
+ "person",
14
+ "clip"
15
+ ];
16
+ //#endregion
17
+ export { FIELD_CONTENT_TYPES };
@@ -0,0 +1,33 @@
1
+ import { WorkspaceViewDirection } from "./view-switcher.logic.js";
2
+ //#region src/view-switcher.d.ts
3
+ type WorkspaceViewSwitcherItem = {
4
+ id: string;
5
+ name: string;
6
+ };
7
+ type WorkspaceViewSwitcherEditing<View> = {
8
+ viewId: string;
9
+ renderLabel: (view: View) => React.ReactNode;
10
+ };
11
+ type WorkspaceViewSwitcherReorder<View> = {
12
+ getDragData?: (view: View) => Record<string | symbol, unknown>;
13
+ getDropData?: (view: View) => Record<string | symbol, unknown>;
14
+ isBlocked?: boolean;
15
+ onReorder: (viewIds: string[]) => void;
16
+ };
17
+ type WorkspaceViewSwitcherProps<View extends WorkspaceViewSwitcherItem> = {
18
+ activeViewId: string;
19
+ ariaLabel: string;
20
+ direction: WorkspaceViewDirection;
21
+ reorder: WorkspaceViewSwitcherReorder<View> | null;
22
+ views: readonly View[];
23
+ addControl?: React.ReactNode;
24
+ editing?: WorkspaceViewSwitcherEditing<View> | null;
25
+ onViewChange: (viewId: string) => void;
26
+ onViewContextMenu?: (view: View, event: React.MouseEvent<HTMLElement>) => void;
27
+ onViewDoubleClick?: (view: View, event: React.MouseEvent<HTMLElement>) => void;
28
+ renderActions?: (view: View) => React.ReactNode;
29
+ renderIcon: (view: View) => React.ReactNode;
30
+ };
31
+ declare const WorkspaceViewSwitcher: <View extends WorkspaceViewSwitcherItem>({ activeViewId, ariaLabel, direction, reorder, views, addControl, editing, onViewChange, onViewContextMenu, onViewDoubleClick, renderActions, renderIcon }: WorkspaceViewSwitcherProps<View>) => import("react").JSX.Element;
32
+ //#endregion
33
+ export { WorkspaceViewSwitcher, WorkspaceViewSwitcherEditing, WorkspaceViewSwitcherItem, WorkspaceViewSwitcherProps, WorkspaceViewSwitcherReorder };
@@ -0,0 +1,159 @@
1
+ "use client";
2
+ import { reorderWorkspaceViewIds, toWorkspaceViewDropPosition } from "./view-switcher.logic.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { cn } from "@stll/ui/utils";
5
+ import { useCallback, useEffect, useRef, useState } from "react";
6
+ import { attachClosestEdge, extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
7
+ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
8
+ import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
9
+ import { Tabs, TabsList, TabsTab } from "@stll/ui/tabs";
10
+ //#region src/view-switcher.tsx
11
+ const VIEW_DRAG_TYPE = "@stll/workspace-ui/view-switcher/drag-type";
12
+ const VIEW_DRAG_ID = "@stll/workspace-ui/view-switcher/view-id";
13
+ const VIEW_DRAG_INSTANCE = "@stll/workspace-ui/view-switcher/instance";
14
+ const useLatestCallback = (callback) => {
15
+ const latest = useRef(callback);
16
+ useEffect(() => {
17
+ latest.current = callback;
18
+ }, [callback]);
19
+ return useCallback((...args) => latest.current(...args), []);
20
+ };
21
+ const WorkspaceViewSwitcher = ({ activeViewId, ariaLabel, direction, reorder, views, addControl, editing, onViewChange, onViewContextMenu, onViewDoubleClick, renderActions, renderIcon }) => {
22
+ const canReorder = reorder !== null;
23
+ const [instanceId] = useState(Symbol);
24
+ const [stripContainer, setStripContainer] = useState(null);
25
+ useEffect(() => {
26
+ if (!stripContainer || !canReorder) return;
27
+ return dropTargetForElements({
28
+ element: stripContainer,
29
+ canDrop: ({ source }) => source.data[VIEW_DRAG_TYPE] === true && source.data[VIEW_DRAG_INSTANCE] === instanceId
30
+ });
31
+ }, [
32
+ canReorder,
33
+ instanceId,
34
+ stripContainer
35
+ ]);
36
+ const handleReorder = (draggedId, targetId, position) => {
37
+ const reordered = reorderWorkspaceViewIds({
38
+ ids: views.map((view) => view.id),
39
+ draggedId,
40
+ targetId,
41
+ position
42
+ });
43
+ if (reordered) reorder?.onReorder(reordered);
44
+ };
45
+ return /* @__PURE__ */ jsxs("div", {
46
+ className: "flex min-w-0 flex-1 items-center gap-1 px-2",
47
+ dir: direction,
48
+ children: [/* @__PURE__ */ jsx("div", {
49
+ className: "min-w-0 flex-1",
50
+ ref: setStripContainer,
51
+ children: /* @__PURE__ */ jsx(Tabs, {
52
+ onValueChange: (value) => {
53
+ if (typeof value === "string") onViewChange(value);
54
+ },
55
+ value: activeViewId,
56
+ children: /* @__PURE__ */ jsx(TabsList, {
57
+ "aria-label": ariaLabel,
58
+ variant: "underline",
59
+ children: views.map((view) => {
60
+ if (editing?.viewId === view.id) return /* @__PURE__ */ jsxs(TabsTab, {
61
+ nativeButton: false,
62
+ render: /* @__PURE__ */ jsx("div", {}),
63
+ value: view.id,
64
+ children: [renderIcon(view), editing.renderLabel(view)]
65
+ }, view.id);
66
+ return /* @__PURE__ */ jsx(WorkspaceViewTab, {
67
+ actions: renderActions?.(view),
68
+ canReorder,
69
+ direction,
70
+ getDragData: reorder?.getDragData,
71
+ getDropData: reorder?.getDropData,
72
+ isDragBlocked: reorder?.isBlocked ?? false,
73
+ instanceId,
74
+ onContextMenu: onViewContextMenu,
75
+ onDoubleClick: onViewDoubleClick,
76
+ onReorder: handleReorder,
77
+ renderIcon,
78
+ view
79
+ }, view.id);
80
+ })
81
+ })
82
+ })
83
+ }), addControl]
84
+ });
85
+ };
86
+ const WorkspaceViewTab = ({ actions, canReorder, direction, getDragData, getDropData, isDragBlocked, instanceId, onContextMenu, onDoubleClick, onReorder, renderIcon, view }) => {
87
+ const [tabContainer, setTabContainer] = useState(null);
88
+ const [dropPosition, setDropPosition] = useState(null);
89
+ const canDrag = useLatestCallback(() => !isDragBlocked);
90
+ const initialData = useLatestCallback(() => ({
91
+ ...getDragData?.(view),
92
+ [VIEW_DRAG_TYPE]: true,
93
+ [VIEW_DRAG_ID]: view.id,
94
+ [VIEW_DRAG_INSTANCE]: instanceId
95
+ }));
96
+ const dropData = useLatestCallback(() => ({
97
+ ...getDropData?.(view),
98
+ [VIEW_DRAG_ID]: view.id
99
+ }));
100
+ const reorder = useLatestCallback(onReorder);
101
+ useEffect(() => {
102
+ if (!tabContainer) return;
103
+ return combine(...canReorder ? [draggable({
104
+ element: tabContainer,
105
+ canDrag,
106
+ getInitialData: initialData
107
+ })] : [], dropTargetForElements({
108
+ element: tabContainer,
109
+ canDrop: ({ source }) => canReorder && source.data[VIEW_DRAG_TYPE] === true && source.data[VIEW_DRAG_INSTANCE] === instanceId && source.data[VIEW_DRAG_ID] !== view.id,
110
+ getData: ({ input, element }) => attachClosestEdge(dropData(), {
111
+ element,
112
+ input,
113
+ allowedEdges: ["left", "right"]
114
+ }),
115
+ getIsSticky: () => true,
116
+ onDrag: ({ self }) => setDropPosition(toWorkspaceViewDropPosition(extractClosestEdge(self.data), direction)),
117
+ onDragLeave: () => setDropPosition(null),
118
+ onDrop: ({ source, self }) => {
119
+ setDropPosition(null);
120
+ const draggedId = source.data[VIEW_DRAG_ID];
121
+ const position = toWorkspaceViewDropPosition(extractClosestEdge(self.data), direction);
122
+ if (typeof draggedId === "string" && position) reorder(draggedId, view.id, position);
123
+ }
124
+ }));
125
+ }, [
126
+ canReorder,
127
+ direction,
128
+ canDrag,
129
+ dropData,
130
+ initialData,
131
+ instanceId,
132
+ reorder,
133
+ tabContainer,
134
+ view.id
135
+ ]);
136
+ return /* @__PURE__ */ jsxs("div", {
137
+ className: "relative",
138
+ ref: setTabContainer,
139
+ children: [
140
+ dropPosition !== null && /* @__PURE__ */ jsx("span", {
141
+ "aria-hidden": "true",
142
+ className: cn("bg-primary pointer-events-none absolute inset-y-1 z-20 w-0.5 rounded-full", dropPosition === "before" ? "start-0" : "end-0")
143
+ }),
144
+ /* @__PURE__ */ jsxs(TabsTab, {
145
+ className: actions === null || actions === void 0 ? void 0 : "pe-6.5",
146
+ onContextMenu: (event) => onContextMenu?.(view, event),
147
+ onDoubleClick: (event) => onDoubleClick?.(view, event),
148
+ value: view.id,
149
+ children: [renderIcon(view), /* @__PURE__ */ jsx("span", {
150
+ className: "max-w-36 truncate",
151
+ children: view.name
152
+ })]
153
+ }),
154
+ actions
155
+ ]
156
+ });
157
+ };
158
+ //#endregion
159
+ export { WorkspaceViewSwitcher };
@@ -0,0 +1,14 @@
1
+ import { Edge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
2
+ //#region src/view-switcher.logic.d.ts
3
+ type WorkspaceViewDirection = "ltr" | "rtl";
4
+ type WorkspaceViewDropPosition = "before" | "after";
5
+ declare const toWorkspaceViewDropPosition: (edge: Edge | null, direction: WorkspaceViewDirection) => WorkspaceViewDropPosition | null;
6
+ type ReorderWorkspaceViewIdsParams = {
7
+ ids: readonly string[];
8
+ draggedId: string;
9
+ targetId: string;
10
+ position: WorkspaceViewDropPosition;
11
+ };
12
+ declare const reorderWorkspaceViewIds: ({ ids, draggedId, targetId, position }: ReorderWorkspaceViewIdsParams) => string[] | null;
13
+ //#endregion
14
+ export { WorkspaceViewDirection, WorkspaceViewDropPosition, reorderWorkspaceViewIds, toWorkspaceViewDropPosition };
@@ -0,0 +1,14 @@
1
+ //#region src/view-switcher.logic.ts
2
+ const toWorkspaceViewDropPosition = (edge, direction) => {
3
+ if (edge !== "left" && edge !== "right") return null;
4
+ return (direction === "rtl" ? edge === "left" : edge === "right") ? "after" : "before";
5
+ };
6
+ const reorderWorkspaceViewIds = ({ ids, draggedId, targetId, position }) => {
7
+ if (draggedId === targetId || !ids.includes(draggedId) || !ids.includes(targetId)) return null;
8
+ const withoutDragged = ids.filter((id) => id !== draggedId);
9
+ const targetIndex = withoutDragged.indexOf(targetId);
10
+ const reordered = withoutDragged.toSpliced(position === "after" ? targetIndex + 1 : targetIndex, 0, draggedId);
11
+ return reordered.every((id, index) => id === ids[index]) ? null : reordered;
12
+ };
13
+ //#endregion
14
+ export { reorderWorkspaceViewIds, toWorkspaceViewDropPosition };