@pasquelin/panels 0.1.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/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/components/Band.d.ts +13 -0
- package/dist/components/Center.d.ts +18 -0
- package/dist/components/IconButton.d.ts +20 -0
- package/dist/components/Panel.d.ts +31 -0
- package/dist/components/PanelFrame.d.ts +23 -0
- package/dist/components/PanelHeader.d.ts +15 -0
- package/dist/components/Panels.d.ts +30 -0
- package/dist/components/Rail.d.ts +23 -0
- package/dist/components/ResizeHandle.d.ts +37 -0
- package/dist/components/Separator.d.ts +6 -0
- package/dist/components/Surface.d.ts +9 -0
- package/dist/components/ZoneEdge.d.ts +17 -0
- package/dist/components/content.d.ts +9 -0
- package/dist/components/labels.d.ts +12 -0
- package/dist/core/clamps.d.ts +55 -0
- package/dist/core/context.d.ts +39 -0
- package/dist/core/cx.d.ts +5 -0
- package/dist/core/hooks/useArrangement.d.ts +36 -0
- package/dist/core/hooks/useContainerFit.d.ts +9 -0
- package/dist/core/hooks/usePanels.d.ts +26 -0
- package/dist/core/hooks/usePointerDrag.d.ts +21 -0
- package/dist/core/hooks/useZone.d.ts +28 -0
- package/dist/core/layoutEffect.d.ts +10 -0
- package/dist/core/persistence.d.ts +30 -0
- package/dist/core/store.d.ts +86 -0
- package/dist/core/types.d.ts +88 -0
- package/dist/cx-CcykAxZN.js +6 -0
- package/dist/cx-YyuC5RtB.cjs +1 -0
- package/dist/dockview/DockviewCenter.d.ts +37 -0
- package/dist/dockview/index.d.ts +3 -0
- package/dist/dockview.cjs +1 -0
- package/dist/dockview.js +39 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +935 -0
- package/dist/styles.css +1 -0
- package/llms-full.txt +1242 -0
- package/llms.txt +150 -0
- package/package.json +99 -0
package/llms.txt
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# @pasquelin/panels
|
|
2
|
+
|
|
3
|
+
> **React panel chassis** (React 19, no runtime dependencies), published to npm (ESM + CJS +
|
|
4
|
+
> types). Icon rails on the edges, five resizable zones around a free centre, a layout that
|
|
5
|
+
> survives a reload. **Headless underneath, repaintable on top** — the components are built on
|
|
6
|
+
> hooks that render nothing, and every visual value is a CSS custom property.
|
|
7
|
+
> MIT. Live: https://pasquelin.github.io/panel/
|
|
8
|
+
|
|
9
|
+
## Mental model (read this first)
|
|
10
|
+
|
|
11
|
+
- **It is a LAYOUT, not a framework.** It draws the frame and remembers the sizes. What goes in
|
|
12
|
+
the panels is the consuming project's business. No state management, no data layer, no icon
|
|
13
|
+
set, no i18n, no opinion about the centre.
|
|
14
|
+
- **`<Panel>` and `<Center>` are DESCRIPTORS**, in the shape of `<Route>`: they return `null` and
|
|
15
|
+
are never rendered where they are written. `<Panels>` reads them out of its children with
|
|
16
|
+
`Children.toArray` and hands their content to the zone they named. They must therefore be
|
|
17
|
+
children of `<Panels>`, not of a component in between.
|
|
18
|
+
- **Not portals, and deliberately so**: a portal puts panel content in a different React tree, so
|
|
19
|
+
context, error boundaries and suspense would stop at the boundary and a panel could not read a
|
|
20
|
+
provider its own file sits under.
|
|
21
|
+
- **One store per chassis**, created not imported (`createPanelsStore`). No module singleton —
|
|
22
|
+
two chassis can live in one application, and a test cannot leak into the next.
|
|
23
|
+
- **What is stored is not what is shown.** Stored: which panel each half holds, and the sizes.
|
|
24
|
+
Resolved at render: what a zone draws (a `solo` panel silences the other half), the size it
|
|
25
|
+
takes (bounded against the opposite zone), whether it takes room at all (the band's two halves
|
|
26
|
+
share one height).
|
|
27
|
+
- **There are no "surfaces" or sections.** An earlier design carried them; they were removed. In
|
|
28
|
+
React the router changes the children — there is nothing to solve.
|
|
29
|
+
- **A panel that should not be offered is a panel you do not declare.** That replaces what a
|
|
30
|
+
capability system would be for.
|
|
31
|
+
|
|
32
|
+
## Install and shape
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pnpm add @pasquelin/panels
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import { Panels, Panel } from '@pasquelin/panels'
|
|
40
|
+
import '@pasquelin/panels/styles.css'
|
|
41
|
+
|
|
42
|
+
type PanelId = 'files' | 'notes'
|
|
43
|
+
|
|
44
|
+
<Panels<PanelId>>
|
|
45
|
+
<Panel<PanelId> id="files" zone="left" title="Files" icon={<Icon />}>
|
|
46
|
+
<FileTree />
|
|
47
|
+
</Panel>
|
|
48
|
+
<Panel<PanelId> id="notes" zone="right" title="Notes" opens={380}>
|
|
49
|
+
<Notes />
|
|
50
|
+
</Panel>
|
|
51
|
+
<Panels.Center>
|
|
52
|
+
<YourApp />
|
|
53
|
+
</Panels.Center>
|
|
54
|
+
</Panels>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`<Panels>` fills its parent: that parent needs a height. On a flex child, `minHeight: 0` is the
|
|
58
|
+
usual missing piece.
|
|
59
|
+
|
|
60
|
+
## Geometry
|
|
61
|
+
|
|
62
|
+
- Zones: `left` `right` `top` `bottomLeft` `bottomRight`. **An empty zone takes no room at all.**
|
|
63
|
+
- Each zone has two halves: `primary` (nearest the window edge) and `secondary`.
|
|
64
|
+
- Panels sharing a zone AND a slot **take turns** — the rail switches between them. Different
|
|
65
|
+
slots show together, stacked.
|
|
66
|
+
- `bottomLeft` + `bottomRight` share **one height**. Whichever is alone runs under the opposite
|
|
67
|
+
column; a side column runs to the foot unless the band's half on its side is drawing.
|
|
68
|
+
- The centre stays at one place in the React tree through every arrangement — moving it would
|
|
69
|
+
unmount whatever engine or canvas it holds.
|
|
70
|
+
|
|
71
|
+
## API
|
|
72
|
+
|
|
73
|
+
### Components
|
|
74
|
+
`Panels` `Panel` `Center` · `Rail` `RailZone` `ZoneEdge` `Band` `PanelFrame` · `Surface`
|
|
75
|
+
`PanelHeader` `IconButton` `Separator` `ResizeHandle`
|
|
76
|
+
|
|
77
|
+
### `<Panels>` props
|
|
78
|
+
`store` `storageKey` `storage` `defaultOpen` `header` `footer` `railHeader` `labels` `theme`
|
|
79
|
+
`className`
|
|
80
|
+
|
|
81
|
+
### `<Panel>` props
|
|
82
|
+
`id` `zone` `slot?` `title` `icon?` `actions?` `opens?` `solo?`
|
|
83
|
+
|
|
84
|
+
### Hooks
|
|
85
|
+
- `usePanels<Id>()` → `{ panels, reveal, close, toggle, isShown, focusedZone, reset }`
|
|
86
|
+
- `useZone<Id>(zone)` → `{ primary, secondary, draws, size, split, focused }`
|
|
87
|
+
- `useArrangement` `useShownIn` `useZoneDraws` `useZoneTakesRoom` `useBandHalves`
|
|
88
|
+
- `useContainerFit(ref)` `usePointerDrag()` `usePanelsState(selector)` `usePanelsActions()`
|
|
89
|
+
|
|
90
|
+
### Core
|
|
91
|
+
`createPanelsStore` `shownIn` `specOf` `zoneDraws` `zoneTakesRoom` `undraggedSizeOf`
|
|
92
|
+
`fitZoneSize` `fitSplit` `sharedSizes` `sizeKeyOf` `MIN_SIZE` `MIN_CENTER` `MIN_SPLIT`
|
|
93
|
+
`DEFAULT_SIZES` · `browserStorage` `memoryStorage` `readLayout` `writeLayout` `LAYOUT_VERSION`
|
|
94
|
+
· `ZONES` `SLOTS` `BOTTOM_ZONES` `ZONES_BY_SIDE` `isBottom` `isHorizontal` `isLeading` `cx`
|
|
95
|
+
|
|
96
|
+
### Optional entry point
|
|
97
|
+
`@pasquelin/panels/dockview` → `DockviewCenter`, for document tabs. Separate so its weight only
|
|
98
|
+
lands on projects that want it. Requires `dockview-react` as a peer.
|
|
99
|
+
|
|
100
|
+
## Driving it from outside React
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
const store = createPanelsStore<PanelId>()
|
|
104
|
+
socket.on('alert', () => store.getState().show('alerts'))
|
|
105
|
+
window.electron?.onMenu(id => store.getState().show(id))
|
|
106
|
+
|
|
107
|
+
<Panels<PanelId> store={store}>…</Panels>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Theming
|
|
111
|
+
|
|
112
|
+
Every value is a custom property on `.pnl-root`. Set them anywhere that reaches the chassis.
|
|
113
|
+
|
|
114
|
+
Surfaces: `--pnl-chassis` `--pnl-panel` `--pnl-elevated` `--pnl-border` `--pnl-text` `--pnl-muted`
|
|
115
|
+
Accent: `--pnl-accent` `--pnl-accent-text`
|
|
116
|
+
Gauges: `--pnl-rail` `--pnl-rail-button` `--pnl-rail-inset` `--pnl-gutter` `--pnl-header`
|
|
117
|
+
`--pnl-radius` `--pnl-radius-sm` `--pnl-font-size`
|
|
118
|
+
|
|
119
|
+
- **Panels are DARKER than the chassis.** That inversion is the one visual idea the library is
|
|
120
|
+
built on. Invert it back by repainting two tokens.
|
|
121
|
+
- Defaults sit inside `:where()`, at zero specificity, so a consumer's palette always wins
|
|
122
|
+
whatever order the stylesheets load in. A guard reads the sheet to keep it that way.
|
|
123
|
+
- `<Panels theme="dark" | "light">` forces the palette; left out it follows `prefers-color-scheme`.
|
|
124
|
+
|
|
125
|
+
## Traps worth knowing
|
|
126
|
+
|
|
127
|
+
- `usePanels` **must be called inside `<Panels>`**. A component that renders the chassis cannot
|
|
128
|
+
also read it — put the consumer in a child, or in a panel's `actions`.
|
|
129
|
+
- Subscribe with **scalar** selectors. `lengths` and `available` are replaced wholesale on every
|
|
130
|
+
write, so subscribing to either as an object re-renders on every frame of a drag.
|
|
131
|
+
- `close(id)` closes THAT panel or nothing. `close(zone, slot)` on the store empties the half
|
|
132
|
+
whatever stands in it — two panels share a half.
|
|
133
|
+
- `useZoneTakesRoom` ≠ `useZoneDraws`: the band's halves share a height, so either drawing means
|
|
134
|
+
the strip takes it.
|
|
135
|
+
- Two chassis in one app need two `storageKey`s, or they overwrite each other.
|
|
136
|
+
- The chassis measures **its container**, never the window — that is what lets it sit inside an
|
|
137
|
+
existing page.
|
|
138
|
+
- Client-side only: it reads `localStorage` and measures the DOM. Under Next.js, `'use client'`.
|
|
139
|
+
|
|
140
|
+
## Not in v1
|
|
141
|
+
|
|
142
|
+
Floating panels, and dragging a panel from one zone to another. The state model already allows
|
|
143
|
+
an override of the declared zone; the gesture is not built.
|
|
144
|
+
|
|
145
|
+
## Documentation
|
|
146
|
+
|
|
147
|
+
- English: docs/en/README.md · PANELS · LAYOUT · HOOKS · THEMING · COMPONENTS · DOCKVIEW · RECIPES
|
|
148
|
+
- Français: docs/fr/README.md (same eight chapters)
|
|
149
|
+
- Architecture: docs/ARCHITECTURE.md
|
|
150
|
+
- Full text of all of it: llms-full.txt
|
package/package.json
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pasquelin/panels",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Châssis à panneaux React : rails d'icônes, zones redimensionnables, centre libre. Headless + composants par défaut, entièrement surchargeable.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"packageManager": "pnpm@11.16.0",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "Alban Pasquelin <alban.pasquelin@gmail.com>",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/pasquelin/panel.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/pasquelin/panel#readme",
|
|
14
|
+
"bugs": "https://github.com/pasquelin/panel/issues",
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
},
|
|
21
|
+
"sideEffects": [
|
|
22
|
+
"**/*.css"
|
|
23
|
+
],
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"llms.txt",
|
|
28
|
+
"llms-full.txt"
|
|
29
|
+
],
|
|
30
|
+
"main": "./dist/index.cjs",
|
|
31
|
+
"module": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.js",
|
|
37
|
+
"require": "./dist/index.cjs"
|
|
38
|
+
},
|
|
39
|
+
"./dockview": {
|
|
40
|
+
"types": "./dist/dockview/index.d.ts",
|
|
41
|
+
"import": "./dist/dockview.js",
|
|
42
|
+
"require": "./dist/dockview.cjs"
|
|
43
|
+
},
|
|
44
|
+
"./styles.css": "./dist/styles.css",
|
|
45
|
+
"./package.json": "./package.json"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"types": "tsc -p tsconfig.build.json",
|
|
49
|
+
"build": "tsc --noEmit && vite build && pnpm types",
|
|
50
|
+
"prepack": "pnpm build",
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"test:watch": "vitest",
|
|
54
|
+
"lint": "eslint src examples",
|
|
55
|
+
"format": "prettier --write \"{src,examples}/**/*.{ts,tsx,css}\"",
|
|
56
|
+
"format:check": "prettier --check \"{src,examples}/**/*.{ts,tsx,css}\"",
|
|
57
|
+
"dev": "vite --config examples/vite.config.ts",
|
|
58
|
+
"build:site": "vite build --config examples/vite.config.ts",
|
|
59
|
+
"preview:site": "vite preview --config examples/vite.config.ts",
|
|
60
|
+
"llms": "node scripts/make-llms-full.mjs",
|
|
61
|
+
"llms:check": "node scripts/make-llms-full.mjs --check",
|
|
62
|
+
"licences:check": "node scripts/check-licences.mjs",
|
|
63
|
+
"validate": "pnpm llms:check && pnpm licences:check && pnpm typecheck && pnpm lint && pnpm format:check && pnpm test"
|
|
64
|
+
},
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"dockview-react": ">=8.0.0",
|
|
67
|
+
"react": "^19.0.0",
|
|
68
|
+
"react-dom": "^19.0.0"
|
|
69
|
+
},
|
|
70
|
+
"peerDependenciesMeta": {
|
|
71
|
+
"dockview-react": {
|
|
72
|
+
"optional": true
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"devDependencies": {
|
|
76
|
+
"@eslint/js": "^10.0.1",
|
|
77
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
78
|
+
"@testing-library/react": "^16.3.2",
|
|
79
|
+
"@testing-library/user-event": "^14.6.4",
|
|
80
|
+
"@types/node": "^24.9.1",
|
|
81
|
+
"@types/react": "^19.2.18",
|
|
82
|
+
"@types/react-dom": "^19.2.4",
|
|
83
|
+
"@vitejs/plugin-react": "^5.2.0",
|
|
84
|
+
"zustand": "^5.0.15",
|
|
85
|
+
"dockview-react": "^8.2.0",
|
|
86
|
+
"eslint": "^10.8.1",
|
|
87
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
88
|
+
"jsdom": "^29.1.1",
|
|
89
|
+
"prettier": "^3.9.6",
|
|
90
|
+
"react": "^19.2.8",
|
|
91
|
+
"react-dom": "^19.2.8",
|
|
92
|
+
"react-router-dom": "^7.18.3",
|
|
93
|
+
"typescript": "^6.0.3",
|
|
94
|
+
"typescript-eslint": "^8.67.0",
|
|
95
|
+
"vite": "^7.1.14",
|
|
96
|
+
"vite-plugin-dts": "^4.5.4",
|
|
97
|
+
"vitest": "^4.1.10"
|
|
98
|
+
}
|
|
99
|
+
}
|