@bensdev/react-sidebar 0.1.0 → 0.3.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/CHANGELOG.md +35 -1
- package/README.md +51 -5
- package/dist/index.cjs +46 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +46 -39
- package/dist/index.js.map +1 -1
- package/dist/styles.css +41 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,41 @@ All notable changes to this project are documented here. Format loosely follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Pre-1.0: breaking changes bump the
|
|
5
5
|
**minor** version, not the major.
|
|
6
6
|
|
|
7
|
-
## [0.
|
|
7
|
+
## [0.2.0] — Unreleased
|
|
8
|
+
|
|
9
|
+
- **Fixed:** `collapsible: false` groups (documented as "children always visible") were silently
|
|
10
|
+
forced into the collapsed rail's flyout/ignore treatment, hiding their entire child list the
|
|
11
|
+
moment the sidebar collapsed — worst for a group with no icon of its own, which rendered as a
|
|
12
|
+
blank row. `collapsible: false` now overrides `collapsedGroupBehavior` and always stays fully
|
|
13
|
+
expanded; an icon-less trigger row is skipped on the rail instead of rendering blank.
|
|
14
|
+
- **Fixed:** nested rows that do render on the rail (the above, or `collapsedGroupBehavior:
|
|
15
|
+
"expand"`) had their icon pushed off-center by depth-based indentation, which only makes sense
|
|
16
|
+
once labels are visible. Indentation now resets to flush on the rail (excluding flyout content,
|
|
17
|
+
see below).
|
|
18
|
+
- **Fixed:** the collapsed-rail **flyout** — one of three `collapsedGroupBehavior` modes, and the
|
|
19
|
+
default — rendered every row inside it icon-only: the rail's label/badge/chevron-hiding rules
|
|
20
|
+
are all more specific than the flyout's "show them again" carve-out, so `display: none` silently
|
|
21
|
+
won regardless of source order. This affected any group with a badge or a nested sub-group (the
|
|
22
|
+
playground's own "Operations" example, badge included, was hit by it). The carve-out is
|
|
23
|
+
`!important` now.
|
|
24
|
+
- **Fixed:** the mobile drawer is portaled to `document.body`, which left an empty `.bsb-root`
|
|
25
|
+
column (sized to the desktop rail's width) sitting in the host layout at the sidebar's original
|
|
26
|
+
position, and left the portaled drawer itself with none of the sidebar's `--bsb-*` tokens,
|
|
27
|
+
resets, or dark-mode detection (all scoped to an `.bsb-root` ancestor the portal boundary broke).
|
|
28
|
+
The in-place wrapper is gone; the theme/reset scope now travels through the portal with the
|
|
29
|
+
drawer instead.
|
|
30
|
+
- Collapse toggle now defaults to the sidebar's own background color (`--bsb-toggle-bg:
|
|
31
|
+
var(--bsb-bg)`) with a stronger elevation shadow and a translucent ring border, so it reads as
|
|
32
|
+
a floating control instead of an oddly-tinted, hard-to-see button. `greenMist`'s toggle tokens
|
|
33
|
+
were updated to match. (This was a specificity bug: a bare `.bsb-toggle` rule tied with the
|
|
34
|
+
`.bsb-root button` reset and lost at rest, leaving the button borderless and near-invisible
|
|
35
|
+
until `:hover`. Published `0.1.0` still has it; fixed here by scoping to `.bsb-root .bsb-toggle`.)
|
|
36
|
+
- Docs: clarified that `collapsible: false` beats `collapsedGroupBehavior` (see above), added a
|
|
37
|
+
"Layout requirements" section covering the `height: 100%` contract `.bsb-nav` scrolling depends
|
|
38
|
+
on and Next.js App Router's fixed CSS import order, and noted the mobile drawer's portal
|
|
39
|
+
behavior.
|
|
40
|
+
|
|
41
|
+
## [0.1.0]
|
|
8
42
|
|
|
9
43
|
Initial extraction from the B-Lines admin/user sidebars into a standalone package.
|
|
10
44
|
|
package/README.md
CHANGED
|
@@ -30,6 +30,40 @@ import '@bensdev/react-sidebar/styles.css';
|
|
|
30
30
|
Import the CSS once, near your app's own global stylesheet (before it, if you want your app's
|
|
31
31
|
CSS to win any conflicts).
|
|
32
32
|
|
|
33
|
+
## Layout requirements
|
|
34
|
+
|
|
35
|
+
`.bsb-root` is `height: 100%`, and its internal `.bsb-nav` is the only element that scrolls
|
|
36
|
+
(`overflow-y: auto`). Both only work if every ancestor between `<Sidebar>` and a definite-height
|
|
37
|
+
container also resolves to a real height — percentage heights fall back to `auto` otherwise. Miss
|
|
38
|
+
this and two things break together: the rail grows to fit its content instead of scrolling
|
|
39
|
+
internally, and the page picks up a scrollbar it shouldn't have — most visible as a height jump
|
|
40
|
+
whenever a group opens/closes.
|
|
41
|
+
|
|
42
|
+
Give your root layout a definite height and let `<Sidebar>` sit as a flex/grid item next to your
|
|
43
|
+
content:
|
|
44
|
+
|
|
45
|
+
```css
|
|
46
|
+
html, body, #root { height: 100%; }
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
<div style={{ display: 'flex', height: '100vh', overflow: 'hidden' }}>
|
|
51
|
+
<Sidebar items={items} />
|
|
52
|
+
<main style={{ flex: 1, overflow: 'auto' }}>{children}</main>
|
|
53
|
+
</div>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
A `min-height` (or no height at all) on that wrapper isn't enough — `min-height` doesn't give
|
|
57
|
+
descendants a *definite* height to resolve `height: 100%` against, so `.bsb-nav` still won't scroll.
|
|
58
|
+
|
|
59
|
+
**CSS import order.** "Near your app's own global stylesheet" above assumes your bundler honors
|
|
60
|
+
source order. Frameworks that bundle by module graph — Next.js App Router in particular — always
|
|
61
|
+
place a component's own imported CSS *after* the root layout's global CSS, regardless of which
|
|
62
|
+
file imports which. If your global CSS and `@bensdev/react-sidebar/styles.css` both set the same
|
|
63
|
+
property on the same selector at equal specificity, the sidebar's rule wins there no matter what
|
|
64
|
+
your source order says — override with a more specific selector (`.my-app .bsb-root`) or
|
|
65
|
+
`!important` instead of relying on import order.
|
|
66
|
+
|
|
33
67
|
## Quick start
|
|
34
68
|
|
|
35
69
|
```tsx
|
|
@@ -134,11 +168,17 @@ Router's `NavLink end`), and `isActive` to override the computed value entirely.
|
|
|
134
168
|
]}
|
|
135
169
|
```
|
|
136
170
|
|
|
137
|
-
A group with `collapsible: false` renders as a static section
|
|
138
|
-
children are always visible — a lighter-weight alternative to a top-level `heading` + flat items
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
171
|
+
A group with `collapsible: false` renders as a static section: its `label` becomes a heading and
|
|
172
|
+
children are always visible — a lighter-weight alternative to a top-level `heading` + flat items.
|
|
173
|
+
That holds on the collapsed icon rail too: `collapsible: false` overrides `collapsedGroupBehavior`
|
|
174
|
+
entirely, so its children never fold into a flyout, and never disappear. Give the group its own
|
|
175
|
+
`icon` if you want a row for it on the rail as well; without one, only the children (each with
|
|
176
|
+
their own icon) render there, the same as a `heading` would.
|
|
177
|
+
|
|
178
|
+
*Collapsible* groups (the default) present differently once the sidebar collapses to its icon
|
|
179
|
+
rail: a hover/focus **flyout** by default (`collapsedGroupBehavior="flyout"`), or set it to
|
|
180
|
+
`"expand"` (rendered inline and indented, same as expanded) or `"ignore"` (trigger only, children
|
|
181
|
+
unreachable while collapsed) instead.
|
|
142
182
|
|
|
143
183
|
## Collapse / expand
|
|
144
184
|
|
|
@@ -172,6 +212,12 @@ The drawer includes a focus trap, Escape-to-close, backdrop-click-to-close, body
|
|
|
172
212
|
respects `prefers-reduced-motion` — all on by default and individually toggleable
|
|
173
213
|
(`trapFocus`, `closeOnEscape`, `closeOnBackdropClick`, `lockScroll`, `reduceMotion`).
|
|
174
214
|
|
|
215
|
+
It's portaled to `document.body` by default (pass `portalTarget` for somewhere else, or
|
|
216
|
+
`disablePortal` to render it in place), so it escapes any `overflow: hidden`/`z-index` ancestor
|
|
217
|
+
your layout has. The sidebar's theme tokens, resets, and dark-mode detection travel with it across
|
|
218
|
+
the portal boundary — no `.bsb-root` wrapper is left behind at the drawer's original position, and
|
|
219
|
+
none of its own CSS is scoped to depend on being a descendant of one.
|
|
220
|
+
|
|
175
221
|
## Theming
|
|
176
222
|
|
|
177
223
|
Every visual value is a `--bsb-*` CSS custom property (see `src/styles.css` for the full list —
|
package/dist/index.cjs
CHANGED
|
@@ -522,10 +522,17 @@ function SidebarGroupRow({ item, depth }) {
|
|
|
522
522
|
children: /* @__PURE__ */ jsxRuntime.jsx(ChevronDownIcon, { size: 14 })
|
|
523
523
|
}
|
|
524
524
|
),
|
|
525
|
-
showTooltip && behavior !== "flyout" && /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { id: tooltipId, className: ctx.classNames.tooltip, children: item.tooltip ?? item.label })
|
|
525
|
+
showTooltip && (!collapsible || behavior !== "flyout") && /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { id: tooltipId, className: ctx.classNames.tooltip, children: item.tooltip ?? item.label })
|
|
526
526
|
]
|
|
527
527
|
}
|
|
528
528
|
);
|
|
529
|
+
if (!collapsible) {
|
|
530
|
+
const showTrigger = item.icon != null || !isRail;
|
|
531
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: cx("bsb-list-item", "bsb-group"), children: [
|
|
532
|
+
showTrigger && trigger,
|
|
533
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { id: panelId, className: cx("bsb-group__panel", ctx.classNames.groupPanel), children: /* @__PURE__ */ jsxRuntime.jsx(SidebarNodeList, { items: item.items, depth: depth + 1 }) })
|
|
534
|
+
] });
|
|
535
|
+
}
|
|
529
536
|
if (isRail && behavior === "flyout") {
|
|
530
537
|
return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: cx("bsb-list-item", "bsb-group", "bsb-group--flyout"), children: [
|
|
531
538
|
trigger,
|
|
@@ -548,7 +555,7 @@ function SidebarGroupRow({ item, depth }) {
|
|
|
548
555
|
}
|
|
549
556
|
return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: cx("bsb-list-item", "bsb-group"), children: [
|
|
550
557
|
trigger,
|
|
551
|
-
|
|
558
|
+
isOpen && /* @__PURE__ */ jsxRuntime.jsx("ul", { id: panelId, className: cx("bsb-group__panel", ctx.classNames.groupPanel), children: /* @__PURE__ */ jsxRuntime.jsx(SidebarNodeList, { items: item.items, depth: depth + 1 }) })
|
|
552
559
|
] });
|
|
553
560
|
}
|
|
554
561
|
function SidebarNode({ item, depth }) {
|
|
@@ -852,7 +859,10 @@ function MobileDrawer({
|
|
|
852
859
|
closeLabel,
|
|
853
860
|
classNames,
|
|
854
861
|
durationMs = 400,
|
|
855
|
-
children
|
|
862
|
+
children,
|
|
863
|
+
rootClassName,
|
|
864
|
+
rootStyle,
|
|
865
|
+
rootDataAttrs
|
|
856
866
|
}) {
|
|
857
867
|
const { mounted, state, onPanelTransitionEnd } = useMountTransition(open, durationMs);
|
|
858
868
|
const panelRef = React21__namespace.useRef(null);
|
|
@@ -864,7 +874,7 @@ function MobileDrawer({
|
|
|
864
874
|
useBodyScrollLock(lockScroll && open);
|
|
865
875
|
if (!mounted) return null;
|
|
866
876
|
const widthValue = typeof width === "number" ? `${width}px` : width;
|
|
867
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
877
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Portal, { target: portalTarget, disabled: disablePortal, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx(rootClassName, "bsb-root--drawer-host"), style: rootStyle, ...rootDataAttrs, children: [
|
|
868
878
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
869
879
|
"div",
|
|
870
880
|
{
|
|
@@ -903,7 +913,7 @@ function MobileDrawer({
|
|
|
903
913
|
]
|
|
904
914
|
}
|
|
905
915
|
)
|
|
906
|
-
] });
|
|
916
|
+
] }) });
|
|
907
917
|
}
|
|
908
918
|
function widthToCss(value) {
|
|
909
919
|
if (value == null) return void 0;
|
|
@@ -1147,35 +1157,32 @@ var Sidebar = React21__namespace.forwardRef(function Sidebar2(props, ref) {
|
|
|
1147
1157
|
)
|
|
1148
1158
|
}
|
|
1149
1159
|
),
|
|
1150
|
-
isMobile && /* @__PURE__ */ jsxRuntime.
|
|
1151
|
-
"
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
)
|
|
1177
|
-
}
|
|
1178
|
-
)
|
|
1160
|
+
isMobile && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1161
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { ref: rootRef, "aria-hidden": "true", style: { display: "none" } }),
|
|
1162
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1163
|
+
MobileDrawer,
|
|
1164
|
+
{
|
|
1165
|
+
open: mobileOpen,
|
|
1166
|
+
onClose: closeMobile,
|
|
1167
|
+
ariaLabel,
|
|
1168
|
+
width: drawerWidth,
|
|
1169
|
+
position,
|
|
1170
|
+
closeOnEscape,
|
|
1171
|
+
closeOnBackdropClick,
|
|
1172
|
+
trapFocus,
|
|
1173
|
+
lockScroll,
|
|
1174
|
+
restoreFocus,
|
|
1175
|
+
portalTarget,
|
|
1176
|
+
disablePortal,
|
|
1177
|
+
closeLabel: "Close navigation",
|
|
1178
|
+
classNames,
|
|
1179
|
+
rootClassName,
|
|
1180
|
+
rootStyle: mergedStyle,
|
|
1181
|
+
rootDataAttrs,
|
|
1182
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(SidebarShell, { ...shellProps, collapsed: false, showToggle: false, onToggle: toggleCollapsed, togglePosition: position, toggleStrategy, toggleLabels: { expand: "Expand sidebar", collapse: "Collapse sidebar" } })
|
|
1183
|
+
}
|
|
1184
|
+
)
|
|
1185
|
+
] })
|
|
1179
1186
|
] });
|
|
1180
1187
|
});
|
|
1181
1188
|
Sidebar.displayName = "Sidebar";
|
|
@@ -1190,7 +1197,7 @@ var greenMist = {
|
|
|
1190
1197
|
radiusAction: "4px",
|
|
1191
1198
|
ease: "cubic-bezier(0.16, 1, 0.3, 1)",
|
|
1192
1199
|
shadowLogo: "0 1px 2px rgba(10,37,16,0.05)",
|
|
1193
|
-
shadowToggle: "0 4px
|
|
1200
|
+
shadowToggle: "0 4px 10px rgba(10,37,16,0.28), 0 1px 3px rgba(10,37,16,0.35)",
|
|
1194
1201
|
shadowTooltip: "0 12px 32px rgba(10,37,16,0.12)",
|
|
1195
1202
|
shadowDrawer: "0 24px 48px rgba(10,37,16,0.16)",
|
|
1196
1203
|
badgeFontSize: "9px",
|
|
@@ -1212,10 +1219,10 @@ var greenMist = {
|
|
|
1212
1219
|
tooltipBg: "#0a2510",
|
|
1213
1220
|
tooltipFg: "#d9eed9",
|
|
1214
1221
|
tooltipBorder: "#175423",
|
|
1215
|
-
toggleBg: "#
|
|
1216
|
-
toggleHoverBg: "#
|
|
1222
|
+
toggleBg: "#175423",
|
|
1223
|
+
toggleHoverBg: "#1f6e2e",
|
|
1217
1224
|
toggleFg: "#ffffff",
|
|
1218
|
-
toggleBorder: "
|
|
1225
|
+
toggleBorder: "rgba(255,255,255,0.14)",
|
|
1219
1226
|
avatarBg: "#2d8a3e",
|
|
1220
1227
|
avatarFg: "#ffffff",
|
|
1221
1228
|
avatarRing: "rgba(79,170,90,0.4)",
|
|
@@ -1251,7 +1258,7 @@ var greenMist = {
|
|
|
1251
1258
|
toggleBg: "#27272a",
|
|
1252
1259
|
toggleHoverBg: "#333333",
|
|
1253
1260
|
toggleFg: "#e2e2e2",
|
|
1254
|
-
toggleBorder: "
|
|
1261
|
+
toggleBorder: "rgba(255,255,255,0.14)",
|
|
1255
1262
|
avatarBg: "#27272a",
|
|
1256
1263
|
avatarFg: "#e2e2e2",
|
|
1257
1264
|
avatarRing: "rgba(255,255,255,0.1)",
|