@galaxy-io/dls 2.10.3 → 2.10.5
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/brand/PoweredBy.js +49 -17
- package/dist/inputs/CronInput.js +1 -1
- package/dist/layout/AppFrame.d.ts +40 -2
- package/dist/layout/AppFrame.js +97 -22
- package/dist/layout/internal/AppFrameContext.d.ts +9 -0
- package/dist/layout/internal/AppFrameContext.js +14 -0
- package/dist/navigation/SidebarNav.d.ts +1 -1
- package/dist/navigation/SidebarNav.js +10 -2
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/dist/brand/PoweredBy.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import Text, { TextSize, TextVariant } from "../text/Text.js";
|
|
2
2
|
import GalaxyAlchemyWordmark from "./GalaxyAlchemyWordmark.js";
|
|
3
3
|
import GalaxyFilamentWordmark from "./GalaxyFilamentWordmark.js";
|
|
4
|
+
import { useAppFrameContext } from "../layout/internal/AppFrameContext.js";
|
|
4
5
|
/* empty css */
|
|
5
6
|
import { styled } from "@linaria/react";
|
|
6
7
|
import { forwardRef } from "react";
|
|
7
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { cx } from "@linaria/core";
|
|
8
10
|
//#region src/brand/PoweredBy.tsx
|
|
9
11
|
/**
|
|
10
12
|
* The credit at the foot of a Galaxy module's sidebar: "Powered by" and the product wordmark, as
|
|
11
13
|
* one link that opens the product's site in a new tab. A 40px row (tall enough to centre on the
|
|
12
14
|
* Galaxy host's 24px theme toggle beside it) with `body_sm` tertiary text and the wordmark sized so
|
|
13
|
-
* every product reads at the same x-height (Filament 15px, Alchemy 12px)
|
|
15
|
+
* every product reads at the same x-height (Filament 15px, Alchemy 12px), its letters on the text's
|
|
16
|
+
* baseline (an inline mark shifted by its own descent, not two boxes centred); its accessible name is
|
|
14
17
|
* `ariaLabel` plus " (opens in a new tab)".
|
|
15
18
|
*
|
|
16
19
|
* `wordmark` is an enum of the product wordmarks, never a node: a product wordmark always stands
|
|
@@ -18,8 +21,9 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
18
21
|
* (`Link isExternal` would draw one after the logo); the new tab is in the name.
|
|
19
22
|
*
|
|
20
23
|
* Hover is a monochrome step on the mark, `text.tertiary` to `text.secondary`, the DLS's own hover
|
|
21
|
-
* idiom (a color change, never opacity). Focus is the `FOCUS_RING`.
|
|
22
|
-
*
|
|
24
|
+
* idiom (a color change, never opacity). Focus is the `FOCUS_RING`. In a collapsed `AppFrame` it
|
|
25
|
+
* renders nothing: the icon rail has no room for the credit. Not a nav destination (`NavItem`),
|
|
26
|
+
* not a body link (`Link`).
|
|
23
27
|
*/
|
|
24
28
|
/** The product whose wordmark the credit shows. */
|
|
25
29
|
var PoweredByWordmark = /* @__PURE__ */ function(PoweredByWordmark) {
|
|
@@ -41,15 +45,30 @@ var POWERED_BY_WORDMARK_TO_SIZE_PX_MAP = {
|
|
|
41
45
|
["FILAMENT"]: 15,
|
|
42
46
|
["ALCHEMY"]: 12
|
|
43
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* How far each wordmark's letters hang below their baseline, as a fraction of the artwork's
|
|
50
|
+
* height: Filament's italic f tail (1200 − 910.55 of 1109 units), Alchemy's y (252 − 201.35 of
|
|
51
|
+
* 252). Measured from the artwork's ink, so the mark can sit on the text's own baseline.
|
|
52
|
+
*/
|
|
53
|
+
/** The baseline shift per wordmark: its letters' descent in px at its size, below the text's baseline. */
|
|
54
|
+
/**
|
|
55
|
+
* Each wordmark on the text's baseline: inline (the wordmarks are blocks), an SVG's bottom edge
|
|
56
|
+
* sits on the baseline, so `vertical-align` drops it by the letters' descent. The browser places the baseline (font metrics,
|
|
57
|
+
* pixel snapping), so the mark and "Powered by" always share it.
|
|
58
|
+
*/
|
|
59
|
+
var POWERED_BY_WORDMARK_TO_BASELINE_CSS_MAP = {
|
|
60
|
+
["FILAMENT"]: "gx-PoweredBy-FILAMENT-f1hilrad",
|
|
61
|
+
["ALCHEMY"]: "gx-PoweredBy-ALCHEMY-a1s356ob"
|
|
62
|
+
};
|
|
44
63
|
/** What is appended to the name, as `Link isExternal` does. */
|
|
45
64
|
var NEW_TAB_SUFFIX = " (opens in a new tab)";
|
|
46
65
|
/**
|
|
47
|
-
* The row: 40px (32 + 8), 8px in from the sides like a `NavItem`'s content
|
|
48
|
-
*
|
|
66
|
+
* The row: 40px (32 + 8), 8px in from the sides like a `NavItem`'s content. The text and the mark
|
|
67
|
+
* share one line (and so one baseline), 8px apart. The radius gives the focus ring its corners.
|
|
49
68
|
*/
|
|
50
69
|
var Root = /*#__PURE__*/ styled("a")({
|
|
51
70
|
name: "Root",
|
|
52
|
-
class: "gx-PoweredBy-
|
|
71
|
+
class: "gx-PoweredBy-r1ez0mfr",
|
|
53
72
|
propsAsIs: false
|
|
54
73
|
});
|
|
55
74
|
/**
|
|
@@ -57,18 +76,31 @@ var Root = /*#__PURE__*/ styled("a")({
|
|
|
57
76
|
* already transitions `color` on `duration.fast`. Declared in this module, whose CSS follows the
|
|
58
77
|
* wordmarks' (it imports them), so it wins over their `text.primary`.
|
|
59
78
|
*/
|
|
60
|
-
var MARK_CSS = "gx-PoweredBy-MARK_CSS-
|
|
79
|
+
var MARK_CSS = "gx-PoweredBy-MARK_CSS-mi05aex";
|
|
61
80
|
/** The wordmark component per product: both take a px `size`, an `ariaLabel` and a class. */
|
|
62
81
|
var POWERED_BY_WORDMARK_TO_COMPONENT_MAP = {
|
|
63
82
|
["FILAMENT"]: GalaxyFilamentWordmark,
|
|
64
83
|
["ALCHEMY"]: GalaxyAlchemyWordmark
|
|
65
84
|
};
|
|
66
|
-
/**
|
|
67
|
-
|
|
85
|
+
/**
|
|
86
|
+
* The line holding "Powered by" and the mark. It never wraps or shrinks, and its line height is the
|
|
87
|
+
* row's 40px, so the mark's descent stays inside the line box and never moves the text: the baseline
|
|
88
|
+
* lands where a centred `body_sm` line puts it, level with the middle of an icon beside the row.
|
|
89
|
+
* Declared after Text's own CSS (this module imports it), so the line height wins.
|
|
90
|
+
*/
|
|
91
|
+
var LABEL_CSS = "gx-PoweredBy-LABEL_CSS-llnwtgc";
|
|
92
|
+
/** The 8px between "Powered by" and the mark, as inline padding (no margins). */
|
|
93
|
+
var MarkSlot = /*#__PURE__*/ styled("span")({
|
|
94
|
+
name: "MarkSlot",
|
|
95
|
+
class: "gx-PoweredBy-MarkSlot-mh367b4",
|
|
96
|
+
propsAsIs: false
|
|
97
|
+
});
|
|
68
98
|
/** The sidebar credit: "Powered by" and a product wordmark, linking to the product in a new tab. */
|
|
69
99
|
var PoweredBy = forwardRef(function PoweredBy({ wordmark, href, ariaLabel, id, className, style, ...rest }, ref) {
|
|
100
|
+
const frame = useAppFrameContext();
|
|
70
101
|
const Wordmark = POWERED_BY_WORDMARK_TO_COMPONENT_MAP[wordmark];
|
|
71
|
-
|
|
102
|
+
if (frame?.isCollapsed) return null;
|
|
103
|
+
return /* @__PURE__ */ jsx(Root, {
|
|
72
104
|
"aria-label": `${ariaLabel}${NEW_TAB_SUFFIX}`,
|
|
73
105
|
...rest,
|
|
74
106
|
ref,
|
|
@@ -78,17 +110,17 @@ var PoweredBy = forwardRef(function PoweredBy({ wordmark, href, ariaLabel, id, c
|
|
|
78
110
|
rel: "noopener noreferrer",
|
|
79
111
|
className,
|
|
80
112
|
style,
|
|
81
|
-
children:
|
|
113
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
82
114
|
as: "span",
|
|
83
115
|
size: TextSize.BODY_SM,
|
|
84
116
|
variant: TextVariant.TERTIARY,
|
|
85
117
|
className: LABEL_CSS,
|
|
86
|
-
children: "Powered by"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
})
|
|
118
|
+
children: ["Powered by", /* @__PURE__ */ jsx(MarkSlot, { children: /* @__PURE__ */ jsx(Wordmark, {
|
|
119
|
+
size: POWERED_BY_WORDMARK_TO_SIZE_PX_MAP[wordmark],
|
|
120
|
+
ariaLabel: "",
|
|
121
|
+
className: cx(MARK_CSS, POWERED_BY_WORDMARK_TO_BASELINE_CSS_MAP[wordmark])
|
|
122
|
+
}) })]
|
|
123
|
+
})
|
|
92
124
|
});
|
|
93
125
|
});
|
|
94
126
|
PoweredBy.displayName = "PoweredBy";
|
package/dist/inputs/CronInput.js
CHANGED
|
@@ -123,7 +123,7 @@ var CRON_INPUT_FOCUS_RETRY_FRAMES = 10;
|
|
|
123
123
|
/** The neutral hint shown when `cronstrue` cannot read the expression. */
|
|
124
124
|
var CRON_INPUT_HINT = "Enter five fields: minute, hour, day of month, month, day of week.";
|
|
125
125
|
/** The caption on the divider between the builder and the expression. */
|
|
126
|
-
var CRON_INPUT_DIVIDER_LABEL = "
|
|
126
|
+
var CRON_INPUT_DIVIDER_LABEL = "OR";
|
|
127
127
|
/** Each builder row's label; the label column is as wide as the longest, whichever rows show. */
|
|
128
128
|
var ROW_LABELS = {
|
|
129
129
|
frequency: "Frequency",
|
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
import { type AriaAttributes, type CSSProperties, type ReactNode } from "react";
|
|
2
|
-
/** The sidebar column's width in px, the same in every Galaxy module (Filament, Alchemy). */
|
|
3
|
-
export declare const APP_FRAME_SIDEBAR_WIDTH =
|
|
2
|
+
/** The sidebar column's default width in px, the same in every Galaxy module (Filament, Alchemy). */
|
|
3
|
+
export declare const APP_FRAME_SIDEBAR_WIDTH = 220;
|
|
4
|
+
/**
|
|
5
|
+
* The collapsed column's width in px: 8px inset, SidebarNav's 8px, a 32px rail item, 8px, 8px. A
|
|
6
|
+
* rail icon's centre lands 1px from where the expanded item drew it.
|
|
7
|
+
*/
|
|
8
|
+
export declare const APP_FRAME_SIDEBAR_COLLAPSED_WIDTH = 64;
|
|
4
9
|
export interface AppFrameProps extends AriaAttributes {
|
|
5
10
|
/** The sidebar column's content: a `SidebarNav` with no `header`. It fills the column. */
|
|
6
11
|
sidebar: ReactNode;
|
|
12
|
+
/**
|
|
13
|
+
* The sidebar column's width while expanded, inset included. A number is px.
|
|
14
|
+
* @default APP_FRAME_SIDEBAR_WIDTH (220)
|
|
15
|
+
*/
|
|
16
|
+
sidebarWidth?: number | string;
|
|
17
|
+
/** Controlled: the sidebar is the icon rail (`APP_FRAME_SIDEBAR_COLLAPSED_WIDTH`). */
|
|
18
|
+
isCollapsed?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Uncontrolled: whether the sidebar starts as the icon rail.
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
defaultIsCollapsed?: boolean;
|
|
24
|
+
/** Called with the next collapsed state when `AppFrameToggle` is pressed. */
|
|
25
|
+
onCollapsedChange?: (isCollapsed: boolean) => void;
|
|
7
26
|
/** The page, usually a `PageLayout`. The column is a flex column that never overflows the frame. */
|
|
8
27
|
children: ReactNode;
|
|
9
28
|
/** DOM id on the root `<div>`. */
|
|
@@ -17,4 +36,23 @@ export interface AppFrameProps extends AriaAttributes {
|
|
|
17
36
|
}
|
|
18
37
|
/** A Galaxy module's frame: the sidebar column, a hairline, and the page. */
|
|
19
38
|
declare const AppFrame: import("react").ForwardRefExoticComponent<AppFrameProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
39
|
+
export interface AppFrameToggleProps extends AriaAttributes {
|
|
40
|
+
/** DOM id on the `<button>`. */
|
|
41
|
+
id?: string;
|
|
42
|
+
/** Any `data-*` attribute, forwarded to the `<button>`. */
|
|
43
|
+
[dataAttribute: `data-${string}`]: string | number | boolean | undefined;
|
|
44
|
+
/** Extra class on the `<button>`. */
|
|
45
|
+
className?: string;
|
|
46
|
+
/** Inline style on the `<button>`; only for setting CSS custom properties. */
|
|
47
|
+
style?: CSSProperties;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The button that collapses and expands the enclosing AppFrame's sidebar: a `TERTIARY` `SMALL`
|
|
51
|
+
* icon button (`SidebarSimpleIcon`, the host's own), named and tooltipped "Collapse sidebar" /
|
|
52
|
+
* "Expand sidebar", with `aria-expanded` and `aria-controls` on the column. Mod+B presses it (the
|
|
53
|
+
* tooltip shows the shortcut, `aria-keyshortcuts` announces it), also from inside a field. Put it in the
|
|
54
|
+
* SidebarNav footer, opposite `PoweredBy` in a `SPACE_BETWEEN` row; on the rail PoweredBy steps out
|
|
55
|
+
* and the row centres the toggle under the icons. Renders nothing outside an AppFrame.
|
|
56
|
+
*/
|
|
57
|
+
export declare const AppFrameToggle: import("react").ForwardRefExoticComponent<AppFrameToggleProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
20
58
|
export default AppFrame;
|
package/dist/layout/AppFrame.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { Orientation } from "../theme/enums.js";
|
|
2
|
+
import { useControlledState } from "../hooks/useControlledState.js";
|
|
3
|
+
import { getCssSizeValue } from "../utils/css.js";
|
|
4
|
+
import { AppFrameContext, useAppFrameContext } from "./internal/AppFrameContext.js";
|
|
2
5
|
import Divider from "./Divider.js";
|
|
6
|
+
import Button, { ButtonSize, ButtonVariant } from "../buttons/Button.js";
|
|
3
7
|
/* empty css */
|
|
4
8
|
import { styled } from "@linaria/react";
|
|
5
|
-
import { forwardRef } from "react";
|
|
9
|
+
import { forwardRef, useId, useMemo } from "react";
|
|
6
10
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { SidebarSimpleIcon } from "@phosphor-icons/react";
|
|
7
12
|
//#region src/layout/AppFrame.tsx
|
|
8
13
|
/**
|
|
9
14
|
* The product frame of a Galaxy module: a fixed sidebar column, a vertical hairline, and a content
|
|
@@ -23,12 +28,29 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
23
28
|
* it a `footer` and its items only, and `hasDividers={false}` when the footer should run on
|
|
24
29
|
* without a line.
|
|
25
30
|
*
|
|
26
|
-
*
|
|
27
|
-
* `
|
|
28
|
-
*
|
|
31
|
+
* The sidebar is `APP_FRAME_SIDEBAR_WIDTH`, 220px, the width every Galaxy module uses;
|
|
32
|
+
* `sidebarWidth` changes it (a wrapper cannot reach the column, so it is the one layout prop here).
|
|
33
|
+
*
|
|
34
|
+
* **Collapsing.** `isCollapsed` / `defaultIsCollapsed` / `onCollapsedChange` narrow the column to
|
|
35
|
+
* the icon rail, `APP_FRAME_SIDEBAR_COLLAPSED_WIDTH` (64px: the inset, SidebarNav's padding and a
|
|
36
|
+
* 32px item), so the icons barely move. The width eases over `duration.base` (the Galaxy host's own
|
|
37
|
+
* sidebar motion) and the column clips while it moves; reduced motion snaps it. A SidebarNav in the
|
|
38
|
+
* column follows the frame (its own `isCollapsed` still wins), `PoweredBy` steps out on the rail,
|
|
39
|
+
* and `AppFrameToggle` is the button that flips it: put it in the nav's footer, at the end of the
|
|
40
|
+
* PoweredBy row; it also binds Mod+B (⌘B on Mac, Ctrl+B elsewhere), the Galaxy host's own sidebar
|
|
41
|
+
* shortcut, while it is mounted.
|
|
42
|
+
*
|
|
43
|
+
* Not a page (`PageLayout`), not resizable panes (`ResizablePanels`).
|
|
44
|
+
*/
|
|
45
|
+
/** The collapse shortcut: Mod+B, ⌘B on Mac and Ctrl+B elsewhere, as in the Galaxy host. */
|
|
46
|
+
var APP_FRAME_TOGGLE_HOT_KEYS = ["mod", "b"];
|
|
47
|
+
/** The sidebar column's default width in px, the same in every Galaxy module (Filament, Alchemy). */
|
|
48
|
+
var APP_FRAME_SIDEBAR_WIDTH = 220;
|
|
49
|
+
/**
|
|
50
|
+
* The collapsed column's width in px: 8px inset, SidebarNav's 8px, a 32px rail item, 8px, 8px. A
|
|
51
|
+
* rail icon's centre lands 1px from where the expanded item drew it.
|
|
29
52
|
*/
|
|
30
|
-
|
|
31
|
-
var APP_FRAME_SIDEBAR_WIDTH = 240;
|
|
53
|
+
var APP_FRAME_SIDEBAR_COLLAPSED_WIDTH = 64;
|
|
32
54
|
/** The frame: a row that fills the host's box on the sidebar's fill, clipped. */
|
|
33
55
|
var Root = /*#__PURE__*/ styled("div")({
|
|
34
56
|
name: "Root",
|
|
@@ -36,9 +58,10 @@ var Root = /*#__PURE__*/ styled("div")({
|
|
|
36
58
|
propsAsIs: false
|
|
37
59
|
});
|
|
38
60
|
/**
|
|
39
|
-
* The sidebar column: fixed width, inset `8 8 0`. With SidebarNav's
|
|
40
|
-
* 16px from the top and both sides, on the Galaxy host's Home button
|
|
41
|
-
* footer runs to the bottom edge.
|
|
61
|
+
* The sidebar column: fixed width (`sidebarWidth`, or the rail), inset `8 8 0`. With SidebarNav's
|
|
62
|
+
* own 8px the first item sits 16px from the top and both sides, on the Galaxy host's Home button
|
|
63
|
+
* (the host contract), and the footer runs to the bottom edge. It clips so the labels never spill
|
|
64
|
+
* while the width moves.
|
|
42
65
|
*/
|
|
43
66
|
var Sidebar = /*#__PURE__*/ styled("div")({
|
|
44
67
|
name: "Sidebar",
|
|
@@ -52,20 +75,72 @@ var Content = /*#__PURE__*/ styled("div")({
|
|
|
52
75
|
propsAsIs: false
|
|
53
76
|
});
|
|
54
77
|
/** A Galaxy module's frame: the sidebar column, a hairline, and the page. */
|
|
55
|
-
var AppFrame = forwardRef(function AppFrame({ sidebar, children, id, className, style, ...rest }, ref) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
78
|
+
var AppFrame = forwardRef(function AppFrame({ sidebar, sidebarWidth = 220, isCollapsed: isCollapsedProp, defaultIsCollapsed = false, onCollapsedChange, children, id, className, style, ...rest }, ref) {
|
|
79
|
+
const [isCollapsed, setIsCollapsed] = useControlledState({
|
|
80
|
+
value: isCollapsedProp,
|
|
81
|
+
defaultValue: defaultIsCollapsed,
|
|
82
|
+
onChange: onCollapsedChange
|
|
83
|
+
});
|
|
84
|
+
const sidebarId = useId();
|
|
85
|
+
const context = useMemo(() => ({
|
|
86
|
+
isCollapsed,
|
|
87
|
+
setIsCollapsed,
|
|
88
|
+
sidebarId
|
|
89
|
+
}), [
|
|
90
|
+
isCollapsed,
|
|
91
|
+
setIsCollapsed,
|
|
92
|
+
sidebarId
|
|
93
|
+
]);
|
|
94
|
+
const width = isCollapsed ? `64px` : getCssSizeValue(sidebarWidth);
|
|
95
|
+
return /* @__PURE__ */ jsx(AppFrameContext.Provider, {
|
|
96
|
+
value: context,
|
|
97
|
+
children: /* @__PURE__ */ jsxs(Root, {
|
|
98
|
+
...rest,
|
|
99
|
+
ref,
|
|
100
|
+
id,
|
|
101
|
+
className,
|
|
102
|
+
style,
|
|
103
|
+
children: [
|
|
104
|
+
/* @__PURE__ */ jsx(Sidebar, {
|
|
105
|
+
id: sidebarId,
|
|
106
|
+
style: { "--gx-app-frame-sidebar-width": width },
|
|
107
|
+
children: sidebar
|
|
108
|
+
}),
|
|
109
|
+
/* @__PURE__ */ jsx(Divider, { orientation: Orientation.VERTICAL }),
|
|
110
|
+
/* @__PURE__ */ jsx(Content, { children })
|
|
111
|
+
]
|
|
112
|
+
})
|
|
67
113
|
});
|
|
68
114
|
});
|
|
69
115
|
AppFrame.displayName = "AppFrame";
|
|
116
|
+
/**
|
|
117
|
+
* The button that collapses and expands the enclosing AppFrame's sidebar: a `TERTIARY` `SMALL`
|
|
118
|
+
* icon button (`SidebarSimpleIcon`, the host's own), named and tooltipped "Collapse sidebar" /
|
|
119
|
+
* "Expand sidebar", with `aria-expanded` and `aria-controls` on the column. Mod+B presses it (the
|
|
120
|
+
* tooltip shows the shortcut, `aria-keyshortcuts` announces it), also from inside a field. Put it in the
|
|
121
|
+
* SidebarNav footer, opposite `PoweredBy` in a `SPACE_BETWEEN` row; on the rail PoweredBy steps out
|
|
122
|
+
* and the row centres the toggle under the icons. Renders nothing outside an AppFrame.
|
|
123
|
+
*/
|
|
124
|
+
var AppFrameToggle = forwardRef(function AppFrameToggle(props, ref) {
|
|
125
|
+
const frame = useAppFrameContext();
|
|
126
|
+
if (frame === null) return null;
|
|
127
|
+
const { isCollapsed, setIsCollapsed, sidebarId } = frame;
|
|
128
|
+
const label = isCollapsed ? "Expand sidebar" : "Collapse sidebar";
|
|
129
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
130
|
+
"aria-expanded": !isCollapsed,
|
|
131
|
+
"aria-controls": sidebarId,
|
|
132
|
+
...props,
|
|
133
|
+
ref,
|
|
134
|
+
variant: ButtonVariant.TERTIARY,
|
|
135
|
+
size: ButtonSize.SMALL,
|
|
136
|
+
icon: SidebarSimpleIcon,
|
|
137
|
+
ariaLabel: label,
|
|
138
|
+
tooltip: label,
|
|
139
|
+
hotKeys: APP_FRAME_TOGGLE_HOT_KEYS,
|
|
140
|
+
shouldBindHotKey: true,
|
|
141
|
+
onClick: () => setIsCollapsed(!isCollapsed)
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
AppFrameToggle.displayName = "AppFrameToggle";
|
|
70
145
|
//#endregion
|
|
71
|
-
export { APP_FRAME_SIDEBAR_WIDTH, AppFrame as default };
|
|
146
|
+
export { APP_FRAME_SIDEBAR_COLLAPSED_WIDTH, APP_FRAME_SIDEBAR_WIDTH, AppFrameToggle, AppFrame as default };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface AppFrameContextValue {
|
|
2
|
+
isCollapsed: boolean;
|
|
3
|
+
setIsCollapsed: (isCollapsed: boolean) => void;
|
|
4
|
+
/** The sidebar column's DOM id, for the toggle's `aria-controls`. */
|
|
5
|
+
sidebarId: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const AppFrameContext: import("react").Context<AppFrameContextValue | null>;
|
|
8
|
+
/** The enclosing AppFrame's collapse state, or `null` outside one. */
|
|
9
|
+
export declare function useAppFrameContext(): AppFrameContextValue | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
//#region src/layout/internal/AppFrameContext.ts
|
|
3
|
+
/**
|
|
4
|
+
* What AppFrame shares with the parts in its sidebar column: whether the column is the icon rail,
|
|
5
|
+
* and the setter `AppFrameToggle` calls. `SidebarNav` reads `isCollapsed` when its own prop is
|
|
6
|
+
* unset; `PoweredBy` renders nothing on the rail. `null` outside an AppFrame.
|
|
7
|
+
*/
|
|
8
|
+
var AppFrameContext = createContext(null);
|
|
9
|
+
/** The enclosing AppFrame's collapse state, or `null` outside one. */
|
|
10
|
+
function useAppFrameContext() {
|
|
11
|
+
return useContext(AppFrameContext);
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { AppFrameContext, useAppFrameContext };
|
|
@@ -13,7 +13,7 @@ export interface SidebarNavProps extends AriaAttributes {
|
|
|
13
13
|
*/
|
|
14
14
|
hasDividers?: boolean;
|
|
15
15
|
/**
|
|
16
|
-
* Icon rail: items show only their icon (labels and counts move into tooltips), group labels become dividers. Give the parent a 48px width.
|
|
16
|
+
* Icon rail: items show only their icon (labels and counts move into tooltips), group labels become dividers. Give the parent a 48px width. Inside an `AppFrame` it follows the frame's collapsed state when unset.
|
|
17
17
|
* @default false
|
|
18
18
|
*/
|
|
19
19
|
isCollapsed?: boolean;
|
|
@@ -3,6 +3,7 @@ import { Placement } from "../theme/enums.js";
|
|
|
3
3
|
import Text, { TextSize, TextTransform, TextVariant, TextWeight } from "../text/Text.js";
|
|
4
4
|
import { useControlledOpenState } from "../hooks/useControlledOpenState.js";
|
|
5
5
|
import Tooltip from "../tooltip/Tooltip.js";
|
|
6
|
+
import { useAppFrameContext } from "../layout/internal/AppFrameContext.js";
|
|
6
7
|
import Divider from "../layout/Divider.js";
|
|
7
8
|
import ScrollArea from "../layout/ScrollArea.js";
|
|
8
9
|
import Icon, { IconVariant } from "../icons/Icon.js";
|
|
@@ -62,6 +63,7 @@ var Header = /*#__PURE__*/ styled("div")({
|
|
|
62
63
|
class: "gx-SidebarNav-Header-h7sesvp",
|
|
63
64
|
propsAsIs: false
|
|
64
65
|
});
|
|
66
|
+
/** Items stand 8px apart, expanded and on the rail: the Galaxy host's own sidebar rhythm. */
|
|
65
67
|
var List = /*#__PURE__*/ styled("ul")({
|
|
66
68
|
name: "List",
|
|
67
69
|
class: "gx-SidebarNav-List-lxg8t2f",
|
|
@@ -130,7 +132,11 @@ var GroupDivider = /*#__PURE__*/ styled("div")({
|
|
|
130
132
|
class: "gx-SidebarNav-GroupDivider-gyqt4ix",
|
|
131
133
|
propsAsIs: false
|
|
132
134
|
});
|
|
133
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* Rail mode: the header content and the footer content center in the 48px column. A section drops
|
|
137
|
+
* its 12px top padding and spaces its divider row like an item: the 24px row centres the line, so
|
|
138
|
+
* the space above it (the list's 8px gap + 11.5px) matches the space below (11.5px + 8px).
|
|
139
|
+
*/
|
|
134
140
|
var ROOT_COLLAPSED_CSS = "gx-SidebarNav-ROOT_COLLAPSED_CSS-rieaizt";
|
|
135
141
|
/** The scrolling list takes the height between header and footer. */
|
|
136
142
|
var LIST_SCROLL_CSS = "gx-SidebarNav-LIST_SCROLL_CSS-lo6ca5i";
|
|
@@ -145,7 +151,9 @@ var ITEM_COLLAPSED_CSS = "gx-SidebarNav-ITEM_COLLAPSED_CSS-i1ietnnw";
|
|
|
145
151
|
var LABEL_CSS = "gx-SidebarNav-LABEL_CSS-l1d4bor0";
|
|
146
152
|
var VISUALLY_HIDDEN_CSS = "gx-SidebarNav-VISUALLY_HIDDEN_CSS-v1rmtlmv";
|
|
147
153
|
/** The primary navigation column: header, scrolling list of items and sections, footer. */
|
|
148
|
-
var SidebarNav = forwardRef(function SidebarNav({ children, header, footer, hasDividers = true, isCollapsed
|
|
154
|
+
var SidebarNav = forwardRef(function SidebarNav({ children, header, footer, hasDividers = true, isCollapsed: isCollapsedProp, ariaLabel, id, className, style, ...rest }, ref) {
|
|
155
|
+
const frame = useAppFrameContext();
|
|
156
|
+
const isCollapsed = isCollapsedProp ?? frame?.isCollapsed ?? false;
|
|
149
157
|
const name = ariaLabel || rest["aria-label"] || (rest["aria-labelledby"] ? void 0 : DEFAULT_ARIA_LABEL);
|
|
150
158
|
const listContext = useMemo(() => ({
|
|
151
159
|
isCollapsed,
|