editor-shell 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 +119 -0
- package/dist/chunk-WJIHF6PY.js +163 -0
- package/dist/chunk-WJIHF6PY.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/rail/index.d.ts +186 -0
- package/dist/rail/index.js +3 -0
- package/dist/rail/index.js.map +1 -0
- package/dist/rail/rail.css +76 -0
- package/package.json +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lewis Liu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# editor-shell
|
|
2
|
+
|
|
3
|
+
Shared **editor-chrome** primitives for the EFFICIENT editors — the owner's own
|
|
4
|
+
editor shell, so every editor draws the same chrome from one source instead of
|
|
5
|
+
hand-rolling and drifting.
|
|
6
|
+
|
|
7
|
+
> **Status: foundation.** Ships one brick — the shared left icon-rail. Not
|
|
8
|
+
> published to npm; no product repo consumes it yet.
|
|
9
|
+
|
|
10
|
+
## Why
|
|
11
|
+
|
|
12
|
+
The storefront editor (`efficient-shop`, Next 16 + Puck) and the campaign
|
|
13
|
+
designer (`efficient-admin-portal`, Vite/React) each hand-rolled their own narrow
|
|
14
|
+
left icon-rail. They had already drifted — button 34px vs 36px, icon 18px vs
|
|
15
|
+
20px, radius 9 vs 8, a CSS-var accent vs a Tailwind one. `EditorRail` is the one
|
|
16
|
+
rail they converge on.
|
|
17
|
+
|
|
18
|
+
## The leaf
|
|
19
|
+
|
|
20
|
+
The storefront's editor chrome renders in **Next-16 server components**, so the
|
|
21
|
+
rail is a **self-contained leaf** at `editor-shell/rail`: it imports nothing heavy
|
|
22
|
+
and touches no `document` / `window` / `localStorage` at import or render, so a
|
|
23
|
+
server component can pull it in safely. `tests/rail-leaf-safety.test.tsx` proves
|
|
24
|
+
it.
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
// A client component in any editor:
|
|
28
|
+
'use client';
|
|
29
|
+
import {
|
|
30
|
+
EditorRail,
|
|
31
|
+
PagesIcon, LayersIcon, StylesIcon, MediaIcon, SitemapIcon, AddIcon,
|
|
32
|
+
} from 'editor-shell/rail';
|
|
33
|
+
import 'editor-shell/rail.css'; // once, from a client/global entry
|
|
34
|
+
|
|
35
|
+
export function LeftRail({ view, onView, onAdd }) {
|
|
36
|
+
return (
|
|
37
|
+
<EditorRail
|
|
38
|
+
ariaLabel="Left panel views"
|
|
39
|
+
items={[
|
|
40
|
+
{ id: 'pages', label: 'Pages', icon: <PagesIcon />, active: view === 'pages', onSelect: () => onView('pages') },
|
|
41
|
+
{ id: 'layers', label: 'Layers', icon: <LayersIcon />, active: view === 'layers', onSelect: () => onView('layers') },
|
|
42
|
+
{ id: 'styles', label: 'Styles', icon: <StylesIcon />, active: view === 'styles', onSelect: () => onView('styles') },
|
|
43
|
+
{ id: 'media', label: 'Media', icon: <MediaIcon />, onSelect: onOpenMedia },
|
|
44
|
+
{ id: 'add', label: 'Add page', icon: <AddIcon />, onSelect: onAdd },
|
|
45
|
+
]}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## API
|
|
52
|
+
|
|
53
|
+
### `<EditorRail items ariaLabel? className? />`
|
|
54
|
+
|
|
55
|
+
A **pure presentational** vertical toolbar. It owns the look, never the app
|
|
56
|
+
state — you decide which item is `active` and what each `onSelect` does.
|
|
57
|
+
|
|
58
|
+
| prop | type | default | notes |
|
|
59
|
+
|---|---|---|---|
|
|
60
|
+
| `items` | `EditorRailItem[]` | — | buttons, top→bottom, in render order |
|
|
61
|
+
| `ariaLabel` | `string` | `"Editor"` | names the toolbar for assistive tech |
|
|
62
|
+
| `className` | `string` | — | merged onto `.es-rail` |
|
|
63
|
+
|
|
64
|
+
### `EditorRailItem`
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
interface EditorRailItem {
|
|
68
|
+
id: string; // React key + selection correlation
|
|
69
|
+
label: string; // aria-label + hover title
|
|
70
|
+
icon: React.ReactNode; // an 18px currentColor glyph — use the shipped icons
|
|
71
|
+
active?: boolean; // present ⇒ view toggle (renders aria-pressed); omit for actions
|
|
72
|
+
disabled?: boolean;
|
|
73
|
+
onSelect?: () => void;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
A **view item** passes `active: true | false` and renders as a toggle
|
|
78
|
+
(`aria-pressed`, `.is-active` highlight). An **action item** (the quick-add "+")
|
|
79
|
+
omits `active` and renders as a plain button.
|
|
80
|
+
|
|
81
|
+
`EditorRailButton` — the single-button building block `EditorRail` maps to — is
|
|
82
|
+
exported too, for bespoke layouts.
|
|
83
|
+
|
|
84
|
+
## Tokens
|
|
85
|
+
|
|
86
|
+
The same values, two ways — `railTokens` (JS) and `rail.css` (CSS custom
|
|
87
|
+
properties on `.es-rail`). Taken verbatim from the storefront editor's `.sf-rail`
|
|
88
|
+
(the visual reference).
|
|
89
|
+
|
|
90
|
+
| token | value | CSS var |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| rail width | `46px` | `--es-rail-w` |
|
|
93
|
+
| rail padding (y) | `8px` | `--es-rail-pad-y` |
|
|
94
|
+
| button gap | `4px` | `--es-rail-gap` |
|
|
95
|
+
| rail radius | `12px` | `--es-rail-radius` |
|
|
96
|
+
| button size | `34px` | `--es-rail-btn-size` |
|
|
97
|
+
| button radius | `9px` | `--es-rail-btn-radius` |
|
|
98
|
+
| icon size | `18px` | `--es-rail-icon-size` |
|
|
99
|
+
| background | `#ffffff` | `--es-rail-bg` |
|
|
100
|
+
| idle icon | `#6b7280` | `--es-rail-fg` |
|
|
101
|
+
| hover bg / fg | `#f3f4f6` / `#111827` | `--es-rail-hover-bg` / `--es-rail-hover-fg` |
|
|
102
|
+
| accent (active) | `#2563eb` | `--es-rail-accent` |
|
|
103
|
+
| accent tint (active bg) | `#eff6ff` | `--es-rail-accent-tint` |
|
|
104
|
+
|
|
105
|
+
Re-theme without new CSS by overriding a var on the element:
|
|
106
|
+
`style={{ ['--es-rail-accent']: brand }}`.
|
|
107
|
+
|
|
108
|
+
## Develop
|
|
109
|
+
|
|
110
|
+
No Node on the dev Mac — everything runs in Docker:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
docker run --rm -v "$PWD":/w -w /w node:26-slim \
|
|
114
|
+
sh -lc "npm install && npm run typecheck && npm test && npm run build"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
- `npm run typecheck` — `tsc` over `src` and the specs.
|
|
118
|
+
- `npm test` — the leaf-safety gate.
|
|
119
|
+
- `npm run build` — `tsup` → `dist/` (root + `rail` subpath) + the copied CSS.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
// src/rail/EditorRailButton.tsx
|
|
4
|
+
function EditorRailButton({
|
|
5
|
+
label,
|
|
6
|
+
icon,
|
|
7
|
+
active,
|
|
8
|
+
disabled,
|
|
9
|
+
onSelect,
|
|
10
|
+
className
|
|
11
|
+
}) {
|
|
12
|
+
const classes = ["es-rail-btn", active ? "is-active" : "", className ?? ""].filter(Boolean).join(" ");
|
|
13
|
+
return /* @__PURE__ */ jsx(
|
|
14
|
+
"button",
|
|
15
|
+
{
|
|
16
|
+
type: "button",
|
|
17
|
+
className: classes,
|
|
18
|
+
title: label,
|
|
19
|
+
"aria-label": label,
|
|
20
|
+
"aria-pressed": typeof active === "boolean" ? active : void 0,
|
|
21
|
+
disabled,
|
|
22
|
+
onClick: disabled ? void 0 : onSelect,
|
|
23
|
+
children: icon
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
function EditorRail({ items, ariaLabel = "Editor", className }) {
|
|
28
|
+
return /* @__PURE__ */ jsx(
|
|
29
|
+
"div",
|
|
30
|
+
{
|
|
31
|
+
className: ["es-rail", className ?? ""].filter(Boolean).join(" "),
|
|
32
|
+
role: "toolbar",
|
|
33
|
+
"aria-orientation": "vertical",
|
|
34
|
+
"aria-label": ariaLabel,
|
|
35
|
+
children: items.map((item) => /* @__PURE__ */ jsx(
|
|
36
|
+
EditorRailButton,
|
|
37
|
+
{
|
|
38
|
+
label: item.label,
|
|
39
|
+
icon: item.icon,
|
|
40
|
+
active: item.active,
|
|
41
|
+
disabled: item.disabled,
|
|
42
|
+
onSelect: item.onSelect
|
|
43
|
+
},
|
|
44
|
+
item.id
|
|
45
|
+
))
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
function Glyph({ size = 18, children, ...rest }) {
|
|
50
|
+
return /* @__PURE__ */ jsx(
|
|
51
|
+
"svg",
|
|
52
|
+
{
|
|
53
|
+
width: size,
|
|
54
|
+
height: size,
|
|
55
|
+
viewBox: "0 0 24 24",
|
|
56
|
+
fill: "none",
|
|
57
|
+
stroke: "currentColor",
|
|
58
|
+
strokeWidth: 1.7,
|
|
59
|
+
strokeLinecap: "round",
|
|
60
|
+
strokeLinejoin: "round",
|
|
61
|
+
"aria-hidden": "true",
|
|
62
|
+
...rest,
|
|
63
|
+
children
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
function PagesIcon(props) {
|
|
68
|
+
return /* @__PURE__ */ jsxs(Glyph, { ...props, children: [
|
|
69
|
+
/* @__PURE__ */ jsx("path", { d: "M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" }),
|
|
70
|
+
/* @__PURE__ */ jsx("path", { d: "M14 3v5h5" }),
|
|
71
|
+
/* @__PURE__ */ jsx("path", { d: "M9 13h6M9 17h6" })
|
|
72
|
+
] });
|
|
73
|
+
}
|
|
74
|
+
function LayersIcon(props) {
|
|
75
|
+
return /* @__PURE__ */ jsxs(Glyph, { ...props, children: [
|
|
76
|
+
/* @__PURE__ */ jsx("path", { d: "M12 3l9 5-9 5-9-5 9-5z" }),
|
|
77
|
+
/* @__PURE__ */ jsx("path", { d: "M3 13l9 5 9-5" })
|
|
78
|
+
] });
|
|
79
|
+
}
|
|
80
|
+
function StylesIcon(props) {
|
|
81
|
+
return /* @__PURE__ */ jsxs(Glyph, { ...props, children: [
|
|
82
|
+
/* @__PURE__ */ jsx("path", { d: "M12 3c-4.5 0-8 3.4-8 7.6 0 3 2.2 4.9 4.8 4.9 1 0 1.6.7 1.6 1.5 0 .5-.3.9-.3 1.4 0 .9.8 1.6 1.9 1.6 4.4 0 8-3.6 8-8C20 6.4 16.5 3 12 3z" }),
|
|
83
|
+
/* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "10", r: "0.6", fill: "currentColor" }),
|
|
84
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "7.8", r: "0.6", fill: "currentColor" }),
|
|
85
|
+
/* @__PURE__ */ jsx("circle", { cx: "15.5", cy: "10", r: "0.6", fill: "currentColor" })
|
|
86
|
+
] });
|
|
87
|
+
}
|
|
88
|
+
function MediaIcon(props) {
|
|
89
|
+
return /* @__PURE__ */ jsxs(Glyph, { ...props, children: [
|
|
90
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
|
|
91
|
+
/* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "9.5", r: "1.5" }),
|
|
92
|
+
/* @__PURE__ */ jsx("path", { d: "M21 16l-5-5-6 6" })
|
|
93
|
+
] });
|
|
94
|
+
}
|
|
95
|
+
function SitemapIcon(props) {
|
|
96
|
+
return /* @__PURE__ */ jsxs(Glyph, { ...props, children: [
|
|
97
|
+
/* @__PURE__ */ jsx("rect", { x: "9", y: "3", width: "6", height: "4.5", rx: "1" }),
|
|
98
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "16.5", width: "6", height: "4.5", rx: "1" }),
|
|
99
|
+
/* @__PURE__ */ jsx("rect", { x: "15", y: "16.5", width: "6", height: "4.5", rx: "1" }),
|
|
100
|
+
/* @__PURE__ */ jsx("path", { d: "M12 7.5v4M6 16.5v-2.5h12v2.5M18 16.5v-2.5" })
|
|
101
|
+
] });
|
|
102
|
+
}
|
|
103
|
+
function AddIcon(props) {
|
|
104
|
+
return /* @__PURE__ */ jsx(Glyph, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M12 5v14M5 12h14" }) });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/rail/tokens.ts
|
|
108
|
+
var railTokens = {
|
|
109
|
+
/** Rail column width. `--sf-rail-w: 46px`. */
|
|
110
|
+
railWidth: 46,
|
|
111
|
+
/** Vertical padding inside the rail (top === bottom). */
|
|
112
|
+
railPaddingY: 8,
|
|
113
|
+
/** Gap between buttons. */
|
|
114
|
+
railGap: 4,
|
|
115
|
+
/** Rail card corner radius. `--sf-panel-radius: 12px`. */
|
|
116
|
+
railRadius: 12,
|
|
117
|
+
/** Button hit-area (square). */
|
|
118
|
+
buttonSize: 34,
|
|
119
|
+
/** Button corner radius. */
|
|
120
|
+
buttonRadius: 9,
|
|
121
|
+
/** Icon glyph size. */
|
|
122
|
+
iconSize: 18,
|
|
123
|
+
/** Hover/active colour transition. */
|
|
124
|
+
transition: "background 0.12s, color 0.12s",
|
|
125
|
+
/** Disabled-button opacity. */
|
|
126
|
+
disabledOpacity: 0.35,
|
|
127
|
+
color: {
|
|
128
|
+
/** Rail card background. `--sf-panel`. */
|
|
129
|
+
background: "#ffffff",
|
|
130
|
+
/** Rail card shadow. `--sf-panel-shadow`. */
|
|
131
|
+
shadow: "0 1px 2px rgba(0, 0, 0, 0.05), 0 10px 30px rgba(0, 0, 0, 0.06)",
|
|
132
|
+
/** Idle icon colour. `--sf-muted` (gray-500). */
|
|
133
|
+
idle: "#6b7280",
|
|
134
|
+
/** Hover background. `--sf-hover` (gray-100). */
|
|
135
|
+
hoverBg: "#f3f4f6",
|
|
136
|
+
/** Hover icon colour. `--sf-text` (gray-900). */
|
|
137
|
+
hoverFg: "#111827",
|
|
138
|
+
/** Active icon colour. `--sf-accent` (blue-600). */
|
|
139
|
+
accent: "#2563eb",
|
|
140
|
+
/** Active background. `--sf-accent-tint` (blue-50). */
|
|
141
|
+
accentTint: "#eff6ff"
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
var railCssVars = {
|
|
145
|
+
railWidth: "--es-rail-w",
|
|
146
|
+
railPaddingY: "--es-rail-pad-y",
|
|
147
|
+
railGap: "--es-rail-gap",
|
|
148
|
+
railRadius: "--es-rail-radius",
|
|
149
|
+
buttonSize: "--es-rail-btn-size",
|
|
150
|
+
buttonRadius: "--es-rail-btn-radius",
|
|
151
|
+
iconSize: "--es-rail-icon-size",
|
|
152
|
+
background: "--es-rail-bg",
|
|
153
|
+
shadow: "--es-rail-shadow",
|
|
154
|
+
idle: "--es-rail-fg",
|
|
155
|
+
hoverBg: "--es-rail-hover-bg",
|
|
156
|
+
hoverFg: "--es-rail-hover-fg",
|
|
157
|
+
accent: "--es-rail-accent",
|
|
158
|
+
accentTint: "--es-rail-accent-tint"
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export { AddIcon, EditorRail, EditorRailButton, LayersIcon, MediaIcon, PagesIcon, SitemapIcon, StylesIcon, railCssVars, railTokens };
|
|
162
|
+
//# sourceMappingURL=chunk-WJIHF6PY.js.map
|
|
163
|
+
//# sourceMappingURL=chunk-WJIHF6PY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/rail/EditorRailButton.tsx","../src/rail/EditorRail.tsx","../src/rail/icons.tsx","../src/rail/tokens.ts"],"names":["jsx"],"mappings":"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","file":"chunk-WJIHF6PY.js","sourcesContent":["import type { EditorRailButtonProps } from './types';\n\n/**\n * A single rail button: an icon in a 34×34 square that hovers, highlights when\n * active, and dims when disabled. Look only — the caller owns the click.\n *\n * `aria-pressed` is emitted **only** when `active` is a real boolean, so a view\n * toggle announces its pressed state while an action button (the quick-add \"+\",\n * whose item omits `active`) stays a plain button rather than a stuck toggle.\n */\nexport function EditorRailButton({\n label,\n icon,\n active,\n disabled,\n onSelect,\n className,\n}: EditorRailButtonProps) {\n const classes = ['es-rail-btn', active ? 'is-active' : '', className ?? '']\n .filter(Boolean)\n .join(' ');\n\n return (\n <button\n type=\"button\"\n className={classes}\n title={label}\n aria-label={label}\n aria-pressed={typeof active === 'boolean' ? active : undefined}\n disabled={disabled}\n onClick={disabled ? undefined : onSelect}\n >\n {icon}\n </button>\n );\n}\n","import { EditorRailButton } from './EditorRailButton';\nimport type { EditorRailProps } from './types';\n\n/**\n * The shared left icon-rail — one slim vertical strip of icon buttons that every\n * EFFICIENT editor uses to switch panels (Pages / Layers / Styles / Media /\n * Sitemap) and to quick-add (\"+\"). It is a **pure presentational switcher**: give\n * it `items[]`, it draws them; which one is `active` and what each does is the\n * host's business.\n *\n * No `'use client'` here on purpose — the rail has no hooks or state, so it is a\n * plain module a Next-16 server component can import and even statically render.\n * It's interactive only through the `onSelect` props the host passes, and the\n * host owns its client boundary (both editors' chrome are already client\n * components), so baking a directive into this leaf would only narrow where it\n * can be imported.\n *\n * A vertical `toolbar` (not a `tablist`), because the rail mixes view *toggles*\n * with plain *actions* like the quick-add — a toolbar of buttons models that\n * honestly, where a tablist would force every button to pretend to be a tab.\n */\nexport function EditorRail({ items, ariaLabel = 'Editor', className }: EditorRailProps) {\n return (\n <div\n className={['es-rail', className ?? ''].filter(Boolean).join(' ')}\n role=\"toolbar\"\n aria-orientation=\"vertical\"\n aria-label={ariaLabel}\n >\n {items.map((item) => (\n <EditorRailButton\n key={item.id}\n label={item.label}\n icon={item.icon}\n active={item.active}\n disabled={item.disabled}\n onSelect={item.onSelect}\n />\n ))}\n </div>\n );\n}\n","import type { ReactNode, SVGProps } from 'react';\n\n/**\n * The rail's bespoke icon set — shipped WITH the rail so no editor has to reach\n * for its own icon library (the storefront uses simple-icons, the admin portal\n * heroicons; neither is this rail's source of truth). The five view glyphs are\n * the exact paths the storefront editor's rail already draws, relocated here so\n * the shared rail is visually identical to its reference; `AddIcon` is new.\n *\n * Pure SVG, no interactivity, no browser globals — these render fine inside a\n * React Server Component. Sized 18×18 by default (the storefront's rail size);\n * pass `size` to override, or let `.es-rail-btn svg` in rail.css drive it.\n */\nexport type RailIconProps = Omit<SVGProps<SVGSVGElement>, 'children'> & { size?: number };\n\nfunction Glyph({ size = 18, children, ...rest }: RailIconProps & { children: ReactNode }) {\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.7}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n {...rest}\n >\n {children}\n </svg>\n );\n}\n\n/** Pages — a document with a folded corner and text lines. */\nexport function PagesIcon(props: RailIconProps) {\n return (\n <Glyph {...props}>\n <path d=\"M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z\" />\n <path d=\"M14 3v5h5\" />\n <path d=\"M9 13h6M9 17h6\" />\n </Glyph>\n );\n}\n\n/** Layers — stacked sheets (the section / component tree). */\nexport function LayersIcon(props: RailIconProps) {\n return (\n <Glyph {...props}>\n <path d=\"M12 3l9 5-9 5-9-5 9-5z\" />\n <path d=\"M3 13l9 5 9-5\" />\n </Glyph>\n );\n}\n\n/** Styles — a paint droplet with palette dots (site styles). */\nexport function StylesIcon(props: RailIconProps) {\n return (\n <Glyph {...props}>\n <path d=\"M12 3c-4.5 0-8 3.4-8 7.6 0 3 2.2 4.9 4.8 4.9 1 0 1.6.7 1.6 1.5 0 .5-.3.9-.3 1.4 0 .9.8 1.6 1.9 1.6 4.4 0 8-3.6 8-8C20 6.4 16.5 3 12 3z\" />\n <circle cx=\"8.5\" cy=\"10\" r=\"0.6\" fill=\"currentColor\" />\n <circle cx=\"12\" cy=\"7.8\" r=\"0.6\" fill=\"currentColor\" />\n <circle cx=\"15.5\" cy=\"10\" r=\"0.6\" fill=\"currentColor\" />\n </Glyph>\n );\n}\n\n/** Media — an image frame with a sun and a mountain (the media library). */\nexport function MediaIcon(props: RailIconProps) {\n return (\n <Glyph {...props}>\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"2\" />\n <circle cx=\"8.5\" cy=\"9.5\" r=\"1.5\" />\n <path d=\"M21 16l-5-5-6 6\" />\n </Glyph>\n );\n}\n\n/** Sitemap — a root node branching to two page nodes (the visual sitemap). */\nexport function SitemapIcon(props: RailIconProps) {\n return (\n <Glyph {...props}>\n <rect x=\"9\" y=\"3\" width=\"6\" height=\"4.5\" rx=\"1\" />\n <rect x=\"3\" y=\"16.5\" width=\"6\" height=\"4.5\" rx=\"1\" />\n <rect x=\"15\" y=\"16.5\" width=\"6\" height=\"4.5\" rx=\"1\" />\n <path d=\"M12 7.5v4M6 16.5v-2.5h12v2.5M18 16.5v-2.5\" />\n </Glyph>\n );\n}\n\n/** Add — the quick-add \"+\", drawn in the same weight and cap style as the set. */\nexport function AddIcon(props: RailIconProps) {\n return (\n <Glyph {...props}>\n <path d=\"M12 5v14M5 12h14\" />\n </Glyph>\n );\n}\n","/**\n * Rail design tokens — the ONE source both apps' rails converge on.\n *\n * Every value below is taken verbatim from the storefront editor's rail\n * (`efficient-shop` → `storefront-editor.css`, the `.sf-rail` / `.sf-rail-btn`\n * rules), which is the visual reference. The admin portal's hand-rolled rail had\n * drifted (button 36 vs 34, icon 20 vs 18, radius 8 vs 9, a Tailwind accent\n * instead of a CSS var); these tokens end that drift.\n *\n * Exposed BOTH ways, per the same value:\n * - `railTokens` — this JS object, for anything that needs the numbers/colours\n * in code (inline styles, a theme bridge, a Storybook control);\n * - `rail.css` — the stylesheet, which declares the identical values as CSS\n * custom properties on `.es-rail` (names listed in `railCssVars`).\n *\n * Numeric fields are pixel magnitudes (unitless) so they compose in code; the\n * CSS applies the `px`.\n */\nexport const railTokens = {\n /** Rail column width. `--sf-rail-w: 46px`. */\n railWidth: 46,\n /** Vertical padding inside the rail (top === bottom). */\n railPaddingY: 8,\n /** Gap between buttons. */\n railGap: 4,\n /** Rail card corner radius. `--sf-panel-radius: 12px`. */\n railRadius: 12,\n /** Button hit-area (square). */\n buttonSize: 34,\n /** Button corner radius. */\n buttonRadius: 9,\n /** Icon glyph size. */\n iconSize: 18,\n /** Hover/active colour transition. */\n transition: 'background 0.12s, color 0.12s',\n /** Disabled-button opacity. */\n disabledOpacity: 0.35,\n color: {\n /** Rail card background. `--sf-panel`. */\n background: '#ffffff',\n /** Rail card shadow. `--sf-panel-shadow`. */\n shadow: '0 1px 2px rgba(0, 0, 0, 0.05), 0 10px 30px rgba(0, 0, 0, 0.06)',\n /** Idle icon colour. `--sf-muted` (gray-500). */\n idle: '#6b7280',\n /** Hover background. `--sf-hover` (gray-100). */\n hoverBg: '#f3f4f6',\n /** Hover icon colour. `--sf-text` (gray-900). */\n hoverFg: '#111827',\n /** Active icon colour. `--sf-accent` (blue-600). */\n accent: '#2563eb',\n /** Active background. `--sf-accent-tint` (blue-50). */\n accentTint: '#eff6ff',\n },\n} as const;\n\nexport type RailTokens = typeof railTokens;\n\n/**\n * The CSS custom-property name behind each token. `rail.css` sets these on\n * `.es-rail`; override any of them on (or above) the rail element to re-theme it\n * — e.g. `style={{ ['--es-rail-accent']: brand }}` — without shipping new CSS.\n */\nexport const railCssVars = {\n railWidth: '--es-rail-w',\n railPaddingY: '--es-rail-pad-y',\n railGap: '--es-rail-gap',\n railRadius: '--es-rail-radius',\n buttonSize: '--es-rail-btn-size',\n buttonRadius: '--es-rail-btn-radius',\n iconSize: '--es-rail-icon-size',\n background: '--es-rail-bg',\n shadow: '--es-rail-shadow',\n idle: '--es-rail-fg',\n hoverBg: '--es-rail-hover-bg',\n hoverFg: '--es-rail-hover-fg',\n accent: '--es-rail-accent',\n accentTint: '--es-rail-accent-tint',\n} as const;\n"]}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, SVGProps } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* One entry in the rail. A rail item is either a *view switch* (Pages, Layers,
|
|
6
|
+
* Styles, …) or an *action* (the quick-add "+"). The shape is the same for both;
|
|
7
|
+
* what tells them apart is `active`:
|
|
8
|
+
*
|
|
9
|
+
* - a view item passes `active: true | false` → it renders as a toggle
|
|
10
|
+
* (`aria-pressed`) and lights up when its surface is open;
|
|
11
|
+
* - an action item omits `active` → it renders as a plain button with no
|
|
12
|
+
* pressed state, because "Add" is a thing you *do*, not a thing you're *in*.
|
|
13
|
+
*
|
|
14
|
+
* The rail is purely presentational: it owns the LOOK, never the app state. The
|
|
15
|
+
* host decides which item is `active` and what each `onSelect` does.
|
|
16
|
+
*/
|
|
17
|
+
interface EditorRailItem {
|
|
18
|
+
/** Stable identity — used as the React key and to correlate selection. */
|
|
19
|
+
id: string;
|
|
20
|
+
/** Accessible name; becomes both the `aria-label` and the hover `title`. */
|
|
21
|
+
label: string;
|
|
22
|
+
/**
|
|
23
|
+
* The glyph. Pass one of the icons this package ships
|
|
24
|
+
* (`<PagesIcon />`, `<AddIcon />`, …) so every editor's rail draws the same
|
|
25
|
+
* set. Any 18×18, `currentColor`-stroked node works.
|
|
26
|
+
*/
|
|
27
|
+
icon: ReactNode;
|
|
28
|
+
/** Present ⇒ this is a view toggle and this is whether its surface is open. */
|
|
29
|
+
active?: boolean;
|
|
30
|
+
/** Dim + non-interactive. */
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
/** Click handler. Skipped while `disabled`. */
|
|
33
|
+
onSelect?: () => void;
|
|
34
|
+
}
|
|
35
|
+
interface EditorRailProps {
|
|
36
|
+
/** The buttons, top to bottom, in render order (place the "+" where you want it). */
|
|
37
|
+
items: EditorRailItem[];
|
|
38
|
+
/** Names the toolbar for assistive tech. Defaults to `"Editor"`. */
|
|
39
|
+
ariaLabel?: string;
|
|
40
|
+
/** Extra class(es) merged onto the rail container. */
|
|
41
|
+
className?: string;
|
|
42
|
+
}
|
|
43
|
+
interface EditorRailButtonProps {
|
|
44
|
+
label: string;
|
|
45
|
+
icon: ReactNode;
|
|
46
|
+
active?: boolean;
|
|
47
|
+
disabled?: boolean;
|
|
48
|
+
onSelect?: () => void;
|
|
49
|
+
className?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The shared left icon-rail — one slim vertical strip of icon buttons that every
|
|
54
|
+
* EFFICIENT editor uses to switch panels (Pages / Layers / Styles / Media /
|
|
55
|
+
* Sitemap) and to quick-add ("+"). It is a **pure presentational switcher**: give
|
|
56
|
+
* it `items[]`, it draws them; which one is `active` and what each does is the
|
|
57
|
+
* host's business.
|
|
58
|
+
*
|
|
59
|
+
* No `'use client'` here on purpose — the rail has no hooks or state, so it is a
|
|
60
|
+
* plain module a Next-16 server component can import and even statically render.
|
|
61
|
+
* It's interactive only through the `onSelect` props the host passes, and the
|
|
62
|
+
* host owns its client boundary (both editors' chrome are already client
|
|
63
|
+
* components), so baking a directive into this leaf would only narrow where it
|
|
64
|
+
* can be imported.
|
|
65
|
+
*
|
|
66
|
+
* A vertical `toolbar` (not a `tablist`), because the rail mixes view *toggles*
|
|
67
|
+
* with plain *actions* like the quick-add — a toolbar of buttons models that
|
|
68
|
+
* honestly, where a tablist would force every button to pretend to be a tab.
|
|
69
|
+
*/
|
|
70
|
+
declare function EditorRail({ items, ariaLabel, className }: EditorRailProps): react.JSX.Element;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A single rail button: an icon in a 34×34 square that hovers, highlights when
|
|
74
|
+
* active, and dims when disabled. Look only — the caller owns the click.
|
|
75
|
+
*
|
|
76
|
+
* `aria-pressed` is emitted **only** when `active` is a real boolean, so a view
|
|
77
|
+
* toggle announces its pressed state while an action button (the quick-add "+",
|
|
78
|
+
* whose item omits `active`) stays a plain button rather than a stuck toggle.
|
|
79
|
+
*/
|
|
80
|
+
declare function EditorRailButton({ label, icon, active, disabled, onSelect, className, }: EditorRailButtonProps): react.JSX.Element;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The rail's bespoke icon set — shipped WITH the rail so no editor has to reach
|
|
84
|
+
* for its own icon library (the storefront uses simple-icons, the admin portal
|
|
85
|
+
* heroicons; neither is this rail's source of truth). The five view glyphs are
|
|
86
|
+
* the exact paths the storefront editor's rail already draws, relocated here so
|
|
87
|
+
* the shared rail is visually identical to its reference; `AddIcon` is new.
|
|
88
|
+
*
|
|
89
|
+
* Pure SVG, no interactivity, no browser globals — these render fine inside a
|
|
90
|
+
* React Server Component. Sized 18×18 by default (the storefront's rail size);
|
|
91
|
+
* pass `size` to override, or let `.es-rail-btn svg` in rail.css drive it.
|
|
92
|
+
*/
|
|
93
|
+
type RailIconProps = Omit<SVGProps<SVGSVGElement>, 'children'> & {
|
|
94
|
+
size?: number;
|
|
95
|
+
};
|
|
96
|
+
/** Pages — a document with a folded corner and text lines. */
|
|
97
|
+
declare function PagesIcon(props: RailIconProps): react.JSX.Element;
|
|
98
|
+
/** Layers — stacked sheets (the section / component tree). */
|
|
99
|
+
declare function LayersIcon(props: RailIconProps): react.JSX.Element;
|
|
100
|
+
/** Styles — a paint droplet with palette dots (site styles). */
|
|
101
|
+
declare function StylesIcon(props: RailIconProps): react.JSX.Element;
|
|
102
|
+
/** Media — an image frame with a sun and a mountain (the media library). */
|
|
103
|
+
declare function MediaIcon(props: RailIconProps): react.JSX.Element;
|
|
104
|
+
/** Sitemap — a root node branching to two page nodes (the visual sitemap). */
|
|
105
|
+
declare function SitemapIcon(props: RailIconProps): react.JSX.Element;
|
|
106
|
+
/** Add — the quick-add "+", drawn in the same weight and cap style as the set. */
|
|
107
|
+
declare function AddIcon(props: RailIconProps): react.JSX.Element;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Rail design tokens — the ONE source both apps' rails converge on.
|
|
111
|
+
*
|
|
112
|
+
* Every value below is taken verbatim from the storefront editor's rail
|
|
113
|
+
* (`efficient-shop` → `storefront-editor.css`, the `.sf-rail` / `.sf-rail-btn`
|
|
114
|
+
* rules), which is the visual reference. The admin portal's hand-rolled rail had
|
|
115
|
+
* drifted (button 36 vs 34, icon 20 vs 18, radius 8 vs 9, a Tailwind accent
|
|
116
|
+
* instead of a CSS var); these tokens end that drift.
|
|
117
|
+
*
|
|
118
|
+
* Exposed BOTH ways, per the same value:
|
|
119
|
+
* - `railTokens` — this JS object, for anything that needs the numbers/colours
|
|
120
|
+
* in code (inline styles, a theme bridge, a Storybook control);
|
|
121
|
+
* - `rail.css` — the stylesheet, which declares the identical values as CSS
|
|
122
|
+
* custom properties on `.es-rail` (names listed in `railCssVars`).
|
|
123
|
+
*
|
|
124
|
+
* Numeric fields are pixel magnitudes (unitless) so they compose in code; the
|
|
125
|
+
* CSS applies the `px`.
|
|
126
|
+
*/
|
|
127
|
+
declare const railTokens: {
|
|
128
|
+
/** Rail column width. `--sf-rail-w: 46px`. */
|
|
129
|
+
readonly railWidth: 46;
|
|
130
|
+
/** Vertical padding inside the rail (top === bottom). */
|
|
131
|
+
readonly railPaddingY: 8;
|
|
132
|
+
/** Gap between buttons. */
|
|
133
|
+
readonly railGap: 4;
|
|
134
|
+
/** Rail card corner radius. `--sf-panel-radius: 12px`. */
|
|
135
|
+
readonly railRadius: 12;
|
|
136
|
+
/** Button hit-area (square). */
|
|
137
|
+
readonly buttonSize: 34;
|
|
138
|
+
/** Button corner radius. */
|
|
139
|
+
readonly buttonRadius: 9;
|
|
140
|
+
/** Icon glyph size. */
|
|
141
|
+
readonly iconSize: 18;
|
|
142
|
+
/** Hover/active colour transition. */
|
|
143
|
+
readonly transition: "background 0.12s, color 0.12s";
|
|
144
|
+
/** Disabled-button opacity. */
|
|
145
|
+
readonly disabledOpacity: 0.35;
|
|
146
|
+
readonly color: {
|
|
147
|
+
/** Rail card background. `--sf-panel`. */
|
|
148
|
+
readonly background: "#ffffff";
|
|
149
|
+
/** Rail card shadow. `--sf-panel-shadow`. */
|
|
150
|
+
readonly shadow: "0 1px 2px rgba(0, 0, 0, 0.05), 0 10px 30px rgba(0, 0, 0, 0.06)";
|
|
151
|
+
/** Idle icon colour. `--sf-muted` (gray-500). */
|
|
152
|
+
readonly idle: "#6b7280";
|
|
153
|
+
/** Hover background. `--sf-hover` (gray-100). */
|
|
154
|
+
readonly hoverBg: "#f3f4f6";
|
|
155
|
+
/** Hover icon colour. `--sf-text` (gray-900). */
|
|
156
|
+
readonly hoverFg: "#111827";
|
|
157
|
+
/** Active icon colour. `--sf-accent` (blue-600). */
|
|
158
|
+
readonly accent: "#2563eb";
|
|
159
|
+
/** Active background. `--sf-accent-tint` (blue-50). */
|
|
160
|
+
readonly accentTint: "#eff6ff";
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
type RailTokens = typeof railTokens;
|
|
164
|
+
/**
|
|
165
|
+
* The CSS custom-property name behind each token. `rail.css` sets these on
|
|
166
|
+
* `.es-rail`; override any of them on (or above) the rail element to re-theme it
|
|
167
|
+
* — e.g. `style={{ ['--es-rail-accent']: brand }}` — without shipping new CSS.
|
|
168
|
+
*/
|
|
169
|
+
declare const railCssVars: {
|
|
170
|
+
readonly railWidth: "--es-rail-w";
|
|
171
|
+
readonly railPaddingY: "--es-rail-pad-y";
|
|
172
|
+
readonly railGap: "--es-rail-gap";
|
|
173
|
+
readonly railRadius: "--es-rail-radius";
|
|
174
|
+
readonly buttonSize: "--es-rail-btn-size";
|
|
175
|
+
readonly buttonRadius: "--es-rail-btn-radius";
|
|
176
|
+
readonly iconSize: "--es-rail-icon-size";
|
|
177
|
+
readonly background: "--es-rail-bg";
|
|
178
|
+
readonly shadow: "--es-rail-shadow";
|
|
179
|
+
readonly idle: "--es-rail-fg";
|
|
180
|
+
readonly hoverBg: "--es-rail-hover-bg";
|
|
181
|
+
readonly hoverFg: "--es-rail-hover-fg";
|
|
182
|
+
readonly accent: "--es-rail-accent";
|
|
183
|
+
readonly accentTint: "--es-rail-accent-tint";
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export { AddIcon, EditorRail, EditorRailButton, type EditorRailButtonProps, type EditorRailItem, type EditorRailProps, LayersIcon, MediaIcon, PagesIcon, type RailIconProps, type RailTokens, SitemapIcon, StylesIcon, railCssVars, railTokens };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* editor-shell — rail styles.
|
|
3
|
+
*
|
|
4
|
+
* The values here mirror `railTokens` in tokens.ts one-for-one (both trace back
|
|
5
|
+
* to the storefront editor's `.sf-rail` rules — the visual reference). They are
|
|
6
|
+
* declared as custom properties ON `.es-rail`, not on `:root`, so importing this
|
|
7
|
+
* stylesheet themes ONLY the rail and never leaks variables into the host page.
|
|
8
|
+
* Re-theme by overriding any `--es-rail-*` var on or above the rail element.
|
|
9
|
+
*
|
|
10
|
+
* Import once from a client entry / global stylesheet:
|
|
11
|
+
* import 'editor-shell/rail.css';
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
.es-rail {
|
|
15
|
+
/* — tokens (see tokens.ts / railCssVars) — */
|
|
16
|
+
--es-rail-w: 46px;
|
|
17
|
+
--es-rail-pad-y: 8px;
|
|
18
|
+
--es-rail-gap: 4px;
|
|
19
|
+
--es-rail-radius: 12px;
|
|
20
|
+
--es-rail-btn-size: 34px;
|
|
21
|
+
--es-rail-btn-radius: 9px;
|
|
22
|
+
--es-rail-icon-size: 18px;
|
|
23
|
+
--es-rail-bg: #ffffff;
|
|
24
|
+
--es-rail-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 10px 30px rgba(0, 0, 0, 0.06);
|
|
25
|
+
--es-rail-fg: #6b7280;
|
|
26
|
+
--es-rail-hover-bg: #f3f4f6;
|
|
27
|
+
--es-rail-hover-fg: #111827;
|
|
28
|
+
--es-rail-accent: #2563eb;
|
|
29
|
+
--es-rail-accent-tint: #eff6ff;
|
|
30
|
+
|
|
31
|
+
flex: none;
|
|
32
|
+
width: var(--es-rail-w);
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
align-items: center;
|
|
36
|
+
gap: var(--es-rail-gap);
|
|
37
|
+
padding: var(--es-rail-pad-y) 0;
|
|
38
|
+
background: var(--es-rail-bg);
|
|
39
|
+
border-radius: var(--es-rail-radius);
|
|
40
|
+
box-shadow: var(--es-rail-shadow);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.es-rail-btn {
|
|
44
|
+
width: var(--es-rail-btn-size);
|
|
45
|
+
height: var(--es-rail-btn-size);
|
|
46
|
+
border: none;
|
|
47
|
+
background: transparent;
|
|
48
|
+
border-radius: var(--es-rail-btn-radius);
|
|
49
|
+
color: var(--es-rail-fg);
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
justify-content: center;
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
transition: background 0.12s, color 0.12s;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.es-rail-btn svg {
|
|
58
|
+
width: var(--es-rail-icon-size);
|
|
59
|
+
height: var(--es-rail-icon-size);
|
|
60
|
+
display: block;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.es-rail-btn:hover:not(:disabled) {
|
|
64
|
+
background: var(--es-rail-hover-bg);
|
|
65
|
+
color: var(--es-rail-hover-fg);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.es-rail-btn.is-active {
|
|
69
|
+
background: var(--es-rail-accent-tint);
|
|
70
|
+
color: var(--es-rail-accent);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.es-rail-btn:disabled {
|
|
74
|
+
opacity: 0.35;
|
|
75
|
+
cursor: default;
|
|
76
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "editor-shell",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared editor-chrome primitives for the EFFICIENT editors. First brick: EditorRail — one shared left icon-rail, consumable as a Next-16-safe leaf.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Lewis Liu",
|
|
7
|
+
"homepage": "https://github.com/Lewislhy/editor-shell#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Lewislhy/editor-shell.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Lewislhy/editor-shell/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"sideEffects": [
|
|
17
|
+
"**/*.css"
|
|
18
|
+
],
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"module": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./rail": {
|
|
28
|
+
"types": "./dist/rail/index.d.ts",
|
|
29
|
+
"import": "./dist/rail/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./rail.css": "./dist/rail/rail.css"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"react": ">=18",
|
|
38
|
+
"react-dom": ">=18"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.0.0",
|
|
43
|
+
"@types/react": "^18.2.0",
|
|
44
|
+
"@types/react-dom": "^18.2.0",
|
|
45
|
+
"esbuild": "^0.27.0",
|
|
46
|
+
"react": "^18.2.0",
|
|
47
|
+
"react-dom": "^18.2.0",
|
|
48
|
+
"tsup": "^8.0.0",
|
|
49
|
+
"typescript": "^5.3.0"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup && cp src/rail/rail.css dist/rail/rail.css",
|
|
53
|
+
"dev": "tsup --watch",
|
|
54
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
|
|
55
|
+
"test": "node scripts/test.mjs",
|
|
56
|
+
"clean": "rm -rf dist"
|
|
57
|
+
},
|
|
58
|
+
"keywords": [
|
|
59
|
+
"react",
|
|
60
|
+
"editor",
|
|
61
|
+
"ui-shell",
|
|
62
|
+
"rail",
|
|
63
|
+
"toolbar",
|
|
64
|
+
"next",
|
|
65
|
+
"rsc",
|
|
66
|
+
"leaf"
|
|
67
|
+
],
|
|
68
|
+
"engines": {
|
|
69
|
+
"node": ">=20"
|
|
70
|
+
}
|
|
71
|
+
}
|