Skip to content

Commit f37e767

Browse files
frankieyanclaude
andcommitted
refactor(sidebar): drive the panel width from a single CSS variable
Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3f990e9 commit f37e767

4 files changed

Lines changed: 36 additions & 13 deletions

File tree

src/sidebar/sidebar.module.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
.panel {
2424
position: relative;
2525
box-sizing: border-box;
26+
width: var(--reactist-sidebar-width);
2627
/*
2728
* `contain: layout` bounds the collapse reflow without `paint`, so consumer
2829
* popovers, focus rings, the resize handle, and an overlay card's shadow are

src/sidebar/sidebar.test.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ describe('Sidebar', () => {
6666
expect(screen.getByTestId('sidebar-panel')).toHaveAttribute('data-align', 'end')
6767
})
6868

69+
it('drives the panel width from a single CSS variable, with no literal width', () => {
70+
renderSidebar({ width: 280 })
71+
const panel = screen.getByTestId('sidebar-panel')
72+
expect(panel.style.getPropertyValue('--reactist-sidebar-width')).toBe('280px')
73+
expect(panel.style.width).toBe('')
74+
})
75+
6976
it('ignores a host `role` so the component owns the rendered role', () => {
7077
renderSidebar({}, { contentProps: { role: 'banner' } })
7178
// Docked, the panel is a neutral div with no role; a host role is ignored.

src/sidebar/sidebar.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -307,10 +307,7 @@ const SidebarContent = React.forwardRef<HTMLDivElement, SidebarContentProps>(
307307

308308
const widthStyle =
309309
width != null
310-
? ({
311-
width: `${width}px`,
312-
[SIDEBAR_WIDTH_VAR]: `${width}px`,
313-
} as React.CSSProperties)
310+
? ({ [SIDEBAR_WIDTH_VAR]: `${width}px` } as React.CSSProperties)
314311
: undefined
315312

316313
const childrenToRender = useDeferredUnmount({ isOpen, unmountOnHide, panelRef })
@@ -468,6 +465,7 @@ function SidebarResizeHandle({
468465
const maxValuePx = maxWidth ?? committedWidth
469466

470467
const { currentValuePx, onDoubleClick, onKeyDown, onPointerDown } = useResizablePanel({
468+
cssVariable: SIDEBAR_WIDTH_VAR,
471469
defaultValuePx: defaultWidth ?? committedWidth,
472470
disabled: !isOpen,
473471
edge,

src/sidebar/use-resizable-panel.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import * as React from 'react'
88
export type ResizablePanelEdge = 'left' | 'right' | 'top' | 'bottom'
99

1010
type UseResizablePanelParams = {
11+
/** When set, read/write this CSS custom property instead of `width` / `height`. */
12+
cssVariable?: string
1113
/** Width restored on a double-click reset. */
1214
defaultValuePx: number
1315
/** When `true`, pointer and keyboard gestures are ignored. */
@@ -65,19 +67,32 @@ function getElementValuePx(
6567
element: HTMLElement | null,
6668
edge: ResizablePanelEdge,
6769
fallbackValuePx: number,
70+
cssVariable?: string,
6871
): number {
6972
if (!element) return fallbackValuePx
7073

7174
const dimension = getDimension(edge)
72-
const inlineValuePx = Number.parseFloat(element.style[dimension])
75+
const inlineValuePx = Number.parseFloat(
76+
cssVariable ? element.style.getPropertyValue(cssVariable) : element.style[dimension],
77+
)
7378
if (Number.isFinite(inlineValuePx) && inlineValuePx > 0) return inlineValuePx
7479

7580
const measuredValuePx = element.getBoundingClientRect()[dimension]
7681
return measuredValuePx > 0 ? measuredValuePx : fallbackValuePx
7782
}
7883

79-
function setElementValuePx(element: HTMLElement | null, edge: ResizablePanelEdge, valuePx: number) {
80-
if (element) element.style[getDimension(edge)] = `${valuePx}px`
84+
function setElementValuePx(
85+
element: HTMLElement | null,
86+
edge: ResizablePanelEdge,
87+
valuePx: number,
88+
cssVariable?: string,
89+
) {
90+
if (!element) return
91+
if (cssVariable) {
92+
element.style.setProperty(cssVariable, `${valuePx}px`)
93+
} else {
94+
element.style[getDimension(edge)] = `${valuePx}px`
95+
}
8196
}
8297

8398
function getActiveElementForRestore(): HTMLElement | null {
@@ -121,6 +136,7 @@ function getKeyboardDeltaPx(edge: ResizablePanelEdge, key: string, stepPx: numbe
121136
* event handlers, the animation-frame callback, or an effect, never during render.
122137
*/
123138
export function useResizablePanel({
139+
cssVariable,
124140
defaultValuePx,
125141
disabled = false,
126142
edge,
@@ -150,7 +166,7 @@ export function useResizablePanel({
150166
function flushPreview() {
151167
if (pendingValueRef.current === null) return
152168

153-
setElementValuePx(panelRef.current, edge, pendingValueRef.current)
169+
setElementValuePx(panelRef.current, edge, pendingValueRef.current, cssVariable)
154170
pendingValueRef.current = null
155171
}
156172

@@ -174,7 +190,7 @@ export function useResizablePanel({
174190
function commitValue(nextValuePx: number) {
175191
const clampedValuePx = clamp(nextValuePx, minValuePx, maxValuePx)
176192

177-
setElementValuePx(panelRef.current, edge, clampedValuePx)
193+
setElementValuePx(panelRef.current, edge, clampedValuePx, cssVariable)
178194
onValueCommit(clampedValuePx)
179195
}
180196

@@ -206,7 +222,7 @@ export function useResizablePanel({
206222
endDrag()
207223

208224
const startValuePx = clamp(
209-
getElementValuePx(panelRef.current, edge, currentValuePx),
225+
getElementValuePx(panelRef.current, edge, currentValuePx, cssVariable),
210226
minValuePx,
211227
maxValuePx,
212228
)
@@ -256,15 +272,16 @@ export function useResizablePanel({
256272
event.preventDefault()
257273
commitValue(
258274
nextValuePx ??
259-
getElementValuePx(panelRef.current, edge, currentValuePx) + (deltaPx ?? 0),
275+
getElementValuePx(panelRef.current, edge, currentValuePx, cssVariable) +
276+
(deltaPx ?? 0),
260277
)
261278
}
262279

263280
React.useEffect(
264281
function reapplyCommittedValue() {
265-
setElementValuePx(panelRef.current, edge, currentValuePx)
282+
setElementValuePx(panelRef.current, edge, currentValuePx, cssVariable)
266283
},
267-
[currentValuePx, edge, panelRef],
284+
[currentValuePx, edge, panelRef, cssVariable],
268285
)
269286

270287
React.useEffect(function cleanupOnUnmount() {

0 commit comments

Comments
 (0)