@stll/workspace-ui 0.7.1 → 0.8.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/view-switcher.js +2 -1
- package/dist/workspace-frame.d.ts +38 -1
- package/dist/workspace-frame.js +111 -18
- package/package.json +2 -2
package/dist/view-switcher.js
CHANGED
|
@@ -6,6 +6,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|
|
6
6
|
import { attachClosestEdge, extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
|
|
7
7
|
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
|
8
8
|
import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
9
|
+
import { TOOLBAR_ROW_HEIGHT } from "@stll/ui/inspector";
|
|
9
10
|
import { Tabs, TabsList, TabsTab } from "@stll/ui/tabs";
|
|
10
11
|
//#region src/view-switcher.tsx
|
|
11
12
|
const VIEW_DRAG_TYPE = "@stll/workspace-ui/view-switcher/drag-type";
|
|
@@ -43,7 +44,7 @@ const WorkspaceViewSwitcher = ({ activeViewId, ariaLabel, direction, reorder, vi
|
|
|
43
44
|
if (reordered) reorder?.onReorder(reordered);
|
|
44
45
|
};
|
|
45
46
|
return /* @__PURE__ */ jsxs("div", {
|
|
46
|
-
className: "flex min-w-0 flex-1 items-center gap-1 px-2",
|
|
47
|
+
className: cn("flex min-w-0 flex-1 items-center gap-1 px-2", TOOLBAR_ROW_HEIGHT),
|
|
47
48
|
dir: direction,
|
|
48
49
|
children: [/* @__PURE__ */ jsx("div", {
|
|
49
50
|
className: "min-w-0 flex-1",
|
|
@@ -10,9 +10,35 @@ type WorkspaceFrameNavigationItem = {
|
|
|
10
10
|
disabled?: boolean;
|
|
11
11
|
onActivate: () => void;
|
|
12
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* Renders the described navigation through the sidebar shell from
|
|
15
|
+
* `@stll/ui/sidebar` instead of the fixed application rail: the same
|
|
16
|
+
* collapsible sidebar Stella's own app mounts, so a host gets the header
|
|
17
|
+
* toggle, the labelled menu while expanded, and the tooltips while collapsed
|
|
18
|
+
* without composing the shell itself.
|
|
19
|
+
*/
|
|
20
|
+
type WorkspaceFrameSidebarNavigation = {
|
|
21
|
+
/** Shown at the start of the header row while the sidebar is expanded,
|
|
22
|
+
* typically a wordmark. The collapse toggle sits at its end. */
|
|
23
|
+
brand?: ReactNode;
|
|
24
|
+
/** Accessible names for the header toggle in each state. */
|
|
25
|
+
toggleLabel: {
|
|
26
|
+
collapse: string;
|
|
27
|
+
expand: string;
|
|
28
|
+
};
|
|
29
|
+
/** Controlled open state, for a host that persists it. */
|
|
30
|
+
open?: boolean;
|
|
31
|
+
onOpenChange?: (open: boolean) => void;
|
|
32
|
+
defaultOpen?: boolean;
|
|
33
|
+
/** Keeps the sidebar collapsed regardless of the requested state, e.g.
|
|
34
|
+
* while an inspector pane needs the width. */
|
|
35
|
+
forceCollapsed?: boolean;
|
|
36
|
+
};
|
|
13
37
|
type WorkspaceFrameNavigation = {
|
|
14
38
|
label: string;
|
|
15
39
|
items: readonly WorkspaceFrameNavigationItem[];
|
|
40
|
+
/** Rail-only: the application rail's header slot. The sidebar
|
|
41
|
+
* presentation owns its header (brand and toggle) and ignores this. */
|
|
16
42
|
header?: ReactNode;
|
|
17
43
|
footer?: ReactNode;
|
|
18
44
|
compact: {
|
|
@@ -22,6 +48,7 @@ type WorkspaceFrameNavigation = {
|
|
|
22
48
|
onOpenChange: (open: boolean) => void;
|
|
23
49
|
trigger: ReactElement | null;
|
|
24
50
|
};
|
|
51
|
+
sidebar?: WorkspaceFrameSidebarNavigation;
|
|
25
52
|
};
|
|
26
53
|
type WorkspaceFrameInspector = {
|
|
27
54
|
pane: ReactNode;
|
|
@@ -67,7 +94,17 @@ type WorkspaceFrameTopBar = {
|
|
|
67
94
|
* Stella's complete workspace frame. Hosts provide route descriptors and
|
|
68
95
|
* actions; this component owns the shell, application rail, end rail, and
|
|
69
96
|
* desktop inspector footprint.
|
|
97
|
+
*
|
|
98
|
+
* The two compositions mount different navigation primitives. `described`
|
|
99
|
+
* renders `navigation.items` through the narrower `ApplicationRail` from
|
|
100
|
+
* `@stll/ui/application-rail` — a fixed-width, always-collapsed rail with no
|
|
101
|
+
* expand/collapse state of its own — or, with `navigation.sidebar`, through
|
|
102
|
+
* the collapsible sidebar shell from `@stll/ui/sidebar`. `host-responsive`
|
|
103
|
+
* passes `navigation` straight through to `WorkspaceShell` and takes no
|
|
104
|
+
* position on what fills it; a host that composes its own sidebar content
|
|
105
|
+
* mounts the shell from `@stll/ui/sidebar` there, as this app's `AppSidebar`
|
|
106
|
+
* does.
|
|
70
107
|
*/
|
|
71
108
|
declare const WorkspaceFrame: (props: WorkspaceFrameProps) => import("react").JSX.Element;
|
|
72
109
|
//#endregion
|
|
73
|
-
export { WorkspaceFrame, WorkspaceFrameEndRail, WorkspaceFrameInspector, WorkspaceFrameNavigation, WorkspaceFrameNavigationItem, WorkspaceFrameProps, WorkspaceFrameTopBar };
|
|
110
|
+
export { WorkspaceFrame, WorkspaceFrameEndRail, WorkspaceFrameInspector, WorkspaceFrameNavigation, WorkspaceFrameNavigationItem, WorkspaceFrameProps, WorkspaceFrameSidebarNavigation, WorkspaceFrameTopBar };
|
package/dist/workspace-frame.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { resolveWorkspaceInspectorPresentation } from "./workspace-frame.logic.js";
|
|
3
|
+
import { PanelLeftIcon } from "lucide-react";
|
|
4
|
+
import { Button } from "@stll/ui/button";
|
|
5
|
+
import { Tooltip, TooltipPopup, TooltipTrigger } from "@stll/ui/tooltip";
|
|
3
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
7
|
import { cn } from "@stll/ui/utils";
|
|
8
|
+
import { InspectorDock, SIDE_RAIL_ICON_BUTTON_SIZE, TOOLBAR_ROW_HEIGHT } from "@stll/ui/inspector";
|
|
5
9
|
import { ApplicationRail, ApplicationRailButton, ApplicationRailContent, ApplicationRailFooter, ApplicationRailHeader, ApplicationRailMenu } from "@stll/ui/application-rail";
|
|
6
|
-
import {
|
|
10
|
+
import { DirectionalIcon } from "@stll/ui/directional-icon";
|
|
7
11
|
import { Sheet, SheetPopup, SheetTitle } from "@stll/ui/sheet";
|
|
12
|
+
import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, useSidebar } from "@stll/ui/sidebar";
|
|
8
13
|
import { useIsMobile } from "@stll/ui/use-mobile";
|
|
9
14
|
import { WorkspaceEndRail, WorkspaceShell } from "@stll/ui/workspace-shell";
|
|
10
15
|
//#region src/workspace-frame.tsx
|
|
@@ -12,6 +17,16 @@ import { WorkspaceEndRail, WorkspaceShell } from "@stll/ui/workspace-shell";
|
|
|
12
17
|
* Stella's complete workspace frame. Hosts provide route descriptors and
|
|
13
18
|
* actions; this component owns the shell, application rail, end rail, and
|
|
14
19
|
* desktop inspector footprint.
|
|
20
|
+
*
|
|
21
|
+
* The two compositions mount different navigation primitives. `described`
|
|
22
|
+
* renders `navigation.items` through the narrower `ApplicationRail` from
|
|
23
|
+
* `@stll/ui/application-rail` — a fixed-width, always-collapsed rail with no
|
|
24
|
+
* expand/collapse state of its own — or, with `navigation.sidebar`, through
|
|
25
|
+
* the collapsible sidebar shell from `@stll/ui/sidebar`. `host-responsive`
|
|
26
|
+
* passes `navigation` straight through to `WorkspaceShell` and takes no
|
|
27
|
+
* position on what fills it; a host that composes its own sidebar content
|
|
28
|
+
* mounts the shell from `@stll/ui/sidebar` there, as this app's `AppSidebar`
|
|
29
|
+
* does.
|
|
15
30
|
*/
|
|
16
31
|
const WorkspaceFrame = (props) => {
|
|
17
32
|
if (props.composition === "host-responsive") return /* @__PURE__ */ jsx(WorkspaceShell, {
|
|
@@ -28,22 +43,9 @@ const DescribedWorkspaceFrame = ({ children, endRail, inspector, navigation, top
|
|
|
28
43
|
hasMobilePresentation: inspector?.mobile !== void 0,
|
|
29
44
|
isCompact
|
|
30
45
|
});
|
|
31
|
-
const desktopNavigation = /* @__PURE__ */
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
/* @__PURE__ */ jsx(ApplicationRailHeader, { children: navigation.header }),
|
|
35
|
-
/* @__PURE__ */ jsx(ApplicationRailContent, { children: /* @__PURE__ */ jsx(ApplicationRailMenu, { children: navigation.items.map((item) => /* @__PURE__ */ jsx(ApplicationRailButton, {
|
|
36
|
-
"aria-current": item.active ? "page" : void 0,
|
|
37
|
-
"aria-label": item.label,
|
|
38
|
-
"data-active": item.active || void 0,
|
|
39
|
-
disabled: item.disabled,
|
|
40
|
-
title: item.label,
|
|
41
|
-
className: "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
|
|
42
|
-
onClick: item.onActivate,
|
|
43
|
-
children: item.icon
|
|
44
|
-
}, item.id)) }) }),
|
|
45
|
-
/* @__PURE__ */ jsx(ApplicationRailFooter, { children: navigation.footer })
|
|
46
|
-
]
|
|
46
|
+
const desktopNavigation = navigation.sidebar === void 0 ? /* @__PURE__ */ jsx(DescribedApplicationRail, { navigation }) : /* @__PURE__ */ jsx(DescribedSidebar, {
|
|
47
|
+
navigation,
|
|
48
|
+
sidebar: navigation.sidebar
|
|
47
49
|
});
|
|
48
50
|
const endDock = inspector && inspectorPresentation === "desktop" ? /* @__PURE__ */ jsx(InspectorDock, {
|
|
49
51
|
rail: /* @__PURE__ */ jsx(WorkspaceEndRail, { ...endRail }),
|
|
@@ -54,7 +56,7 @@ const DescribedWorkspaceFrame = ({ children, endRail, inspector, navigation, top
|
|
|
54
56
|
onResetWidth: inspector.onResetWidth,
|
|
55
57
|
children: inspector.pane
|
|
56
58
|
}) : /* @__PURE__ */ jsx(WorkspaceEndRail, { ...endRail });
|
|
57
|
-
const
|
|
59
|
+
const shell = /* @__PURE__ */ jsx(WorkspaceShell, {
|
|
58
60
|
endDock,
|
|
59
61
|
navigation: {
|
|
60
62
|
compact: navigation.compact,
|
|
@@ -81,6 +83,10 @@ const DescribedWorkspaceFrame = ({ children, endRail, inspector, navigation, top
|
|
|
81
83
|
}),
|
|
82
84
|
children
|
|
83
85
|
});
|
|
86
|
+
const frame = navigation.sidebar === void 0 ? shell : /* @__PURE__ */ jsx(SidebarProvider, {
|
|
87
|
+
...resolveSidebarProviderProps(navigation.sidebar),
|
|
88
|
+
children: shell
|
|
89
|
+
});
|
|
84
90
|
if (inspector?.mobile === void 0) return frame;
|
|
85
91
|
return /* @__PURE__ */ jsxs(Sheet, {
|
|
86
92
|
open: inspectorPresentation === "mobile" && inspector.mobile.open,
|
|
@@ -98,5 +104,92 @@ const DescribedWorkspaceFrame = ({ children, endRail, inspector, navigation, top
|
|
|
98
104
|
}) : null]
|
|
99
105
|
});
|
|
100
106
|
};
|
|
107
|
+
/**
|
|
108
|
+
* Only the sidebar options the host actually set reach the provider: an
|
|
109
|
+
* explicit `undefined` would otherwise turn a controlled `open` into an
|
|
110
|
+
* uncontrolled one, or clear the provider's own default.
|
|
111
|
+
*/
|
|
112
|
+
const resolveSidebarProviderProps = ({ defaultOpen, forceCollapsed, open, onOpenChange }) => ({
|
|
113
|
+
...defaultOpen === void 0 ? {} : { defaultOpen },
|
|
114
|
+
...forceCollapsed === void 0 ? {} : { forceCollapsed },
|
|
115
|
+
...open === void 0 ? {} : { open },
|
|
116
|
+
...onOpenChange === void 0 ? {} : { onOpenChange }
|
|
117
|
+
});
|
|
118
|
+
const DescribedApplicationRail = ({ navigation }) => /* @__PURE__ */ jsxs(ApplicationRail, {
|
|
119
|
+
"aria-label": navigation.label,
|
|
120
|
+
children: [
|
|
121
|
+
/* @__PURE__ */ jsx(ApplicationRailHeader, { children: navigation.header }),
|
|
122
|
+
/* @__PURE__ */ jsx(ApplicationRailContent, { children: /* @__PURE__ */ jsx(ApplicationRailMenu, { children: navigation.items.map((item) => /* @__PURE__ */ jsx(ApplicationRailButton, {
|
|
123
|
+
"aria-current": item.active ? "page" : void 0,
|
|
124
|
+
"aria-label": item.label,
|
|
125
|
+
"data-active": item.active || void 0,
|
|
126
|
+
disabled: item.disabled,
|
|
127
|
+
title: item.label,
|
|
128
|
+
className: "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
|
|
129
|
+
onClick: item.onActivate,
|
|
130
|
+
children: item.icon
|
|
131
|
+
}, item.id)) }) }),
|
|
132
|
+
/* @__PURE__ */ jsx(ApplicationRailFooter, { children: navigation.footer })
|
|
133
|
+
]
|
|
134
|
+
});
|
|
135
|
+
/**
|
|
136
|
+
* The described items as Stella's collapsible sidebar. The header is the
|
|
137
|
+
* same row Stella's app shows: brand at the start while expanded, the
|
|
138
|
+
* collapse toggle at the end, at toolbar-row height so it lines up with the
|
|
139
|
+
* top bar beside it. Each item is a sidebar menu button, which shows its
|
|
140
|
+
* label while expanded and a tooltip while collapsed.
|
|
141
|
+
*/
|
|
142
|
+
const DescribedSidebar = ({ navigation, sidebar }) => {
|
|
143
|
+
const { isMobile, state, toggleSidebar } = useSidebar();
|
|
144
|
+
const collapsed = state === "collapsed" && !isMobile;
|
|
145
|
+
const toggleLabel = collapsed ? sidebar.toggleLabel.expand : sidebar.toggleLabel.collapse;
|
|
146
|
+
return /* @__PURE__ */ jsxs(Sidebar, {
|
|
147
|
+
"aria-label": navigation.label,
|
|
148
|
+
collapsible: "icon",
|
|
149
|
+
mobileTitle: navigation.label,
|
|
150
|
+
children: [
|
|
151
|
+
/* @__PURE__ */ jsx(SidebarHeader, {
|
|
152
|
+
className: cn("border-b p-0", TOOLBAR_ROW_HEIGHT),
|
|
153
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
154
|
+
className: cn("flex h-full items-center", collapsed ? "justify-center" : "justify-between ps-3 pe-2"),
|
|
155
|
+
children: [collapsed ? null : sidebar.brand, /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, { render: /* @__PURE__ */ jsx(Button, {
|
|
156
|
+
"aria-label": toggleLabel,
|
|
157
|
+
className: cn("text-muted-foreground", SIDE_RAIL_ICON_BUTTON_SIZE),
|
|
158
|
+
"data-slot": "workspace-frame-sidebar-toggle",
|
|
159
|
+
size: "icon",
|
|
160
|
+
variant: "ghost",
|
|
161
|
+
onClick: toggleSidebar,
|
|
162
|
+
children: /* @__PURE__ */ jsx(DirectionalIcon, {
|
|
163
|
+
className: "size-4",
|
|
164
|
+
icon: PanelLeftIcon
|
|
165
|
+
})
|
|
166
|
+
}) }), /* @__PURE__ */ jsx(TooltipPopup, {
|
|
167
|
+
side: "right",
|
|
168
|
+
children: toggleLabel
|
|
169
|
+
})] })]
|
|
170
|
+
})
|
|
171
|
+
}),
|
|
172
|
+
/* @__PURE__ */ jsx(SidebarContent, { children: /* @__PURE__ */ jsx("nav", {
|
|
173
|
+
"aria-label": navigation.label,
|
|
174
|
+
children: /* @__PURE__ */ jsx(SidebarGroup, {
|
|
175
|
+
className: "group-data-[collapsible=icon]:px-0.5",
|
|
176
|
+
children: /* @__PURE__ */ jsx(SidebarMenu, { children: navigation.items.map((item) => /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
|
|
177
|
+
"aria-current": item.active ? "page" : void 0,
|
|
178
|
+
disabled: item.disabled,
|
|
179
|
+
isActive: item.active ?? false,
|
|
180
|
+
size: "rail",
|
|
181
|
+
tooltip: item.label,
|
|
182
|
+
onClick: item.onActivate,
|
|
183
|
+
children: [item.icon, /* @__PURE__ */ jsx("span", {
|
|
184
|
+
className: "truncate",
|
|
185
|
+
children: item.label
|
|
186
|
+
})]
|
|
187
|
+
}) }, item.id)) })
|
|
188
|
+
})
|
|
189
|
+
}) }),
|
|
190
|
+
/* @__PURE__ */ jsx(SidebarFooter, { children: navigation.footer })
|
|
191
|
+
]
|
|
192
|
+
});
|
|
193
|
+
};
|
|
101
194
|
//#endregion
|
|
102
195
|
export { WorkspaceFrame };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/workspace-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Reusable workspace presentation components and view helpers for Stella.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"calculations",
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"@stll/calculations": "^0.1.0",
|
|
120
120
|
"@stll/conditions": "^0.3.0",
|
|
121
121
|
"@stll/money": "^0.1.0",
|
|
122
|
-
"@stll/ui": "^0.
|
|
122
|
+
"@stll/ui": "^0.20.0",
|
|
123
123
|
"better-result": "3.0.0",
|
|
124
124
|
"lucide-react": "1.30.0",
|
|
125
125
|
"use-intl": "4.13.5"
|