@zccz14/ux 0.1.6 → 0.2.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/README.md +12 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +77 -77
- package/dist/styles.css +72 -25
- package/dist/types.d.ts +1 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
- **Sidebar** — one animated icon-collapse sidebar: 16rem expanded, 3rem collapsed, 200 ms width transition. Midas, 1Exchange and 1earn-style shells without animation are replaced by this behavior.
|
|
8
8
|
- **Navigation groups only** — the sidebar has no separate bottom section. Settings and status destinations live in the navigation groups like every other destination, so the bottom edge of every app's sidebar looks the same.
|
|
9
9
|
- **AppHeader** — sidebar trigger, current page title, your `headerSlot`, and the Linkit account control (`LinkitMyInfo`), in that order. Role badges such as 超级管理员 stay out of the shell: administrator affordances live in the administrator navigation group and pages.
|
|
10
|
-
- **Logo** — you pass
|
|
11
|
-
- **Linkit integration** — the
|
|
10
|
+
- **Logo** — you pass one mark; the shell renders it inside a fixed 1.75rem box and controls the display size itself, so the logo size is identical everywhere. Pass a mark that follows the current colour (an icon using `currentColor`, or an SVG with mode-aware fills) so it keeps working in both themes. The title beside the logo disappears while collapsed.
|
|
11
|
+
- **Linkit integration** — the viewer's language selects the shell copy (中文/English), and theme, language and account controls all live in the `LinkitMyInfo` dialog. The shell never reads the theme itself: it renders whatever colour scheme its own CSS resolves (see [Styling](#styling)).
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
npm install @zccz14/ux
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Peer dependencies: `react` (18 or 19), `react-dom`, `react-router-dom` 7, `@base-ui/react` ≥ 1.6, `linkit-react-components` ≥ 0.
|
|
19
|
+
Peer dependencies: `react` (18 or 19), `react-dom`, `react-router-dom` 7, `@base-ui/react` ≥ 1.6, `linkit-react-components` ≥ 0.6.
|
|
20
20
|
|
|
21
21
|
## Usage
|
|
22
22
|
|
|
@@ -41,7 +41,7 @@ export function App() {
|
|
|
41
41
|
<LinkitProvider linkitBaseUrl="https://linkit.ntnl.io" lang="zh-CN">
|
|
42
42
|
<HashRouter>
|
|
43
43
|
<AppLayout
|
|
44
|
-
logo={
|
|
44
|
+
logo={<CybionMark />}
|
|
45
45
|
title="Cybion"
|
|
46
46
|
nav={[
|
|
47
47
|
{
|
|
@@ -74,7 +74,7 @@ export function App() {
|
|
|
74
74
|
|
|
75
75
|
| Prop | Type | Required | Description |
|
|
76
76
|
| --- | --- | --- | --- |
|
|
77
|
-
| `logo` | `
|
|
77
|
+
| `logo` | `ReactNode` | yes | The logo mark. The shell renders it at its own fixed 1.75rem size; pass a mark that follows the current colour. |
|
|
78
78
|
| `title` | `string` | yes | App name beside the logo. Hidden while the sidebar is collapsed. |
|
|
79
79
|
| `nav` | `AppNavGroup[]` | yes | Navigation groups rendered in the sidebar. |
|
|
80
80
|
| `headerSlot` | `ReactNode` | no | Rendered immediately left of `LinkitMyInfo` in the AppHeader. Use it for page-level actions such as refresh. |
|
|
@@ -101,14 +101,18 @@ type AppNavGroup = { label: string; items: AppNavItem[] };
|
|
|
101
101
|
|
|
102
102
|
## Styling
|
|
103
103
|
|
|
104
|
-
Import `@zccz14/ux/styles.css` once. It consumes the app's shadcn theme tokens
|
|
104
|
+
Import `@zccz14/ux/styles.css` once. It consumes the app's shadcn theme tokens and falls back to its own values, so the shell stays readable even when a token is missing: `--background`, `--foreground`, `--border`, `--accent`, `--muted`, `--ring`, `--sidebar`, `--sidebar-foreground`, `--sidebar-accent`, `--sidebar-accent-foreground`, `--sidebar-border`, `--sidebar-ring`.
|
|
105
|
+
|
|
106
|
+
Those tokens are read through a `--ux-*` layer declared twice — a light set on `:root` and a dark set on `.dark` — so every fallback has a dark counterpart. A missing `--sidebar` therefore renders dark in dark mode instead of a light sidebar. The shell also sets `color-scheme` per mode, so native scrollbars and controls follow the theme without help from a theme provider.
|
|
107
|
+
|
|
108
|
+
Theme state itself is not the shell's business: whatever toggles `.dark` on `<html>` (in this family, `LinkitProvider`) decides the mode, and the shell follows.
|
|
105
109
|
|
|
106
110
|
## Migrating an app from its local shell
|
|
107
111
|
|
|
108
112
|
1. Replace the local `SidebarProvider` / `Sidebar` / `AppHeader` copy with one `AppLayout` at the top of `App.tsx`.
|
|
109
|
-
2. Move legacy header buttons (refresh, settings, language, theme) into `headerSlot`. Role badges are dropped: administrator affordances belong to the administrator navigation group and pages, and theme/language preferences live in the `LinkitMyInfo` profile dialog (`linkit-react-components` 0.
|
|
113
|
+
2. Move legacy header buttons (refresh, settings, language, theme) into `headerSlot`. Role badges are dropped: administrator affordances belong to the administrator navigation group and pages, and theme/language preferences live in the `LinkitMyInfo` profile dialog (`linkit-react-components` 0.6+).
|
|
110
114
|
3. Move any sidebar footer item (settings, environment status) into the last navigation group.
|
|
111
|
-
4. Pass the logo
|
|
115
|
+
4. Pass the single logo mark and delete per-call sizes; the shell normalizes every mark to its 1.75rem box.
|
|
112
116
|
|
|
113
117
|
## Development
|
|
114
118
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { AppLayout } from "./app-layout.js";
|
|
2
|
-
export type { AppLayoutProps,
|
|
2
|
+
export type { AppLayoutProps, AppNavGroup, AppNavItem } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import { useState as
|
|
3
|
-
import { Dialog as
|
|
4
|
-
import { Tooltip as
|
|
5
|
-
import { useLinkit as
|
|
6
|
-
import { useLocation as
|
|
1
|
+
import { jsxs as s, jsx as e, Fragment as y } from "react/jsx-runtime";
|
|
2
|
+
import { useState as f, useCallback as g, useEffect as w, useSyncExternalStore as M } from "react";
|
|
3
|
+
import { Dialog as h } from "@base-ui/react/dialog";
|
|
4
|
+
import { Tooltip as p } from "@base-ui/react/tooltip";
|
|
5
|
+
import { useLinkit as L, LinkitMyInfo as S } from "linkit-react-components";
|
|
6
|
+
import { useLocation as v, NavLink as C } from "react-router-dom";
|
|
7
7
|
const O = {
|
|
8
8
|
closeNavigation: "Close navigation",
|
|
9
9
|
navigation: "Navigation",
|
|
@@ -16,8 +16,8 @@ const O = {
|
|
|
16
16
|
function I(a) {
|
|
17
17
|
return a.toLowerCase().startsWith("zh") ? P : O;
|
|
18
18
|
}
|
|
19
|
-
function
|
|
20
|
-
return /* @__PURE__ */
|
|
19
|
+
function E() {
|
|
20
|
+
return /* @__PURE__ */ s(
|
|
21
21
|
"svg",
|
|
22
22
|
{
|
|
23
23
|
"aria-hidden": "true",
|
|
@@ -34,8 +34,8 @@ function T() {
|
|
|
34
34
|
}
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
|
-
function
|
|
38
|
-
return /* @__PURE__ */
|
|
37
|
+
function T() {
|
|
38
|
+
return /* @__PURE__ */ s(
|
|
39
39
|
"svg",
|
|
40
40
|
{
|
|
41
41
|
"aria-hidden": "true",
|
|
@@ -52,65 +52,65 @@ function E() {
|
|
|
52
52
|
}
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
|
-
const
|
|
55
|
+
const m = "sidebar_state", W = 3600 * 24 * 7, N = "(max-width: 767px)";
|
|
56
56
|
function $() {
|
|
57
|
-
const a = document.cookie.split("; ").find((n) => n.startsWith(`${
|
|
58
|
-
return a ? a.slice(
|
|
57
|
+
const a = document.cookie.split("; ").find((n) => n.startsWith(`${m}=`));
|
|
58
|
+
return a ? a.slice(m.length + 1) !== "false" : !0;
|
|
59
59
|
}
|
|
60
60
|
function j(a) {
|
|
61
|
-
document.cookie = `${
|
|
61
|
+
document.cookie = `${m}=${a}; path=/; max-age=${W}`;
|
|
62
62
|
}
|
|
63
63
|
function B(a) {
|
|
64
|
-
const n = window.matchMedia(
|
|
64
|
+
const n = window.matchMedia(N);
|
|
65
65
|
return n.addEventListener("change", a), () => n.removeEventListener("change", a);
|
|
66
66
|
}
|
|
67
67
|
function A() {
|
|
68
|
-
return
|
|
68
|
+
return M(
|
|
69
69
|
B,
|
|
70
|
-
() => window.matchMedia(
|
|
70
|
+
() => window.matchMedia(N).matches,
|
|
71
71
|
() => !1
|
|
72
72
|
);
|
|
73
73
|
}
|
|
74
74
|
function D() {
|
|
75
|
-
const a = A(), [n, t] =
|
|
76
|
-
t(
|
|
77
|
-
}, []), u =
|
|
75
|
+
const a = A(), [n, t] = f($), [l, r] = f(!1), d = g((c) => {
|
|
76
|
+
t(c), j(c);
|
|
77
|
+
}, []), u = g(() => {
|
|
78
78
|
if (a) {
|
|
79
|
-
|
|
79
|
+
r((c) => !c);
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
82
82
|
d(!n);
|
|
83
83
|
}, [a, n, d]);
|
|
84
|
-
return
|
|
85
|
-
function
|
|
86
|
-
(
|
|
84
|
+
return w(() => {
|
|
85
|
+
function c(i) {
|
|
86
|
+
(i.metaKey || i.ctrlKey) && i.key === "b" && (i.preventDefault(), u());
|
|
87
87
|
}
|
|
88
|
-
return window.addEventListener("keydown",
|
|
89
|
-
}, [u]), { isMobile: a, open: n, openMobile:
|
|
88
|
+
return window.addEventListener("keydown", c), () => window.removeEventListener("keydown", c);
|
|
89
|
+
}, [u]), { isMobile: a, open: n, openMobile: l, setOpenMobile: r, toggleSidebar: u };
|
|
90
90
|
}
|
|
91
|
-
function
|
|
91
|
+
function k(a, n) {
|
|
92
92
|
let t;
|
|
93
|
-
for (const
|
|
94
|
-
for (const
|
|
95
|
-
(
|
|
93
|
+
for (const l of a)
|
|
94
|
+
for (const r of l.items)
|
|
95
|
+
(r.to === "/" ? n === "/" : n === r.to || n.startsWith(`${r.to}/`)) && (!t || r.to.length > t.to.length) && (t = r);
|
|
96
96
|
return t;
|
|
97
97
|
}
|
|
98
98
|
function K({
|
|
99
99
|
collapsed: a,
|
|
100
100
|
end: n,
|
|
101
101
|
item: t,
|
|
102
|
-
onNavigate:
|
|
102
|
+
onNavigate: l
|
|
103
103
|
}) {
|
|
104
|
-
return /* @__PURE__ */
|
|
105
|
-
/* @__PURE__ */
|
|
106
|
-
|
|
104
|
+
return /* @__PURE__ */ s(p.Root, { children: [
|
|
105
|
+
/* @__PURE__ */ s(
|
|
106
|
+
p.Trigger,
|
|
107
107
|
{
|
|
108
108
|
render: /* @__PURE__ */ e(
|
|
109
109
|
C,
|
|
110
110
|
{
|
|
111
111
|
className: "ux-nav-item",
|
|
112
112
|
end: n,
|
|
113
|
-
onClick:
|
|
113
|
+
onClick: l,
|
|
114
114
|
to: t.to
|
|
115
115
|
}
|
|
116
116
|
),
|
|
@@ -120,102 +120,102 @@ function K({
|
|
|
120
120
|
]
|
|
121
121
|
}
|
|
122
122
|
),
|
|
123
|
-
/* @__PURE__ */ e(
|
|
124
|
-
|
|
123
|
+
/* @__PURE__ */ e(p.Portal, { children: /* @__PURE__ */ e(
|
|
124
|
+
p.Positioner,
|
|
125
125
|
{
|
|
126
126
|
className: "ux-tooltip-positioner",
|
|
127
127
|
side: "right",
|
|
128
128
|
sideOffset: 6,
|
|
129
|
-
children: /* @__PURE__ */ e(
|
|
129
|
+
children: /* @__PURE__ */ e(p.Popup, { className: "ux-tooltip", hidden: !a, children: t.label })
|
|
130
130
|
}
|
|
131
131
|
) })
|
|
132
132
|
] });
|
|
133
133
|
}
|
|
134
|
-
function
|
|
134
|
+
function x({
|
|
135
135
|
copy: a,
|
|
136
136
|
logo: n,
|
|
137
137
|
nav: t,
|
|
138
|
-
onNavigate:
|
|
139
|
-
state:
|
|
138
|
+
onNavigate: l,
|
|
139
|
+
state: r,
|
|
140
140
|
title: d
|
|
141
141
|
}) {
|
|
142
|
-
const {
|
|
143
|
-
return /* @__PURE__ */
|
|
144
|
-
/* @__PURE__ */
|
|
145
|
-
/* @__PURE__ */ e("span", { className: "ux-sidebar__logo", children:
|
|
142
|
+
const { pathname: u } = v(), c = k(t, u);
|
|
143
|
+
return /* @__PURE__ */ s(y, { children: [
|
|
144
|
+
/* @__PURE__ */ s("div", { className: "ux-sidebar__header", children: [
|
|
145
|
+
/* @__PURE__ */ e("span", { className: "ux-sidebar__logo", children: n }),
|
|
146
146
|
/* @__PURE__ */ e("span", { className: "ux-sidebar__title", children: d })
|
|
147
147
|
] }),
|
|
148
|
-
/* @__PURE__ */ e("nav", { "aria-label": a.navigation, className: "ux-sidebar__nav", children: t.map((
|
|
149
|
-
/* @__PURE__ */ e("div", { className: "ux-nav-group__label", children:
|
|
150
|
-
|
|
148
|
+
/* @__PURE__ */ e("nav", { "aria-label": a.navigation, className: "ux-sidebar__nav", children: t.map((i) => /* @__PURE__ */ s("div", { className: "ux-nav-group", children: [
|
|
149
|
+
/* @__PURE__ */ e("div", { className: "ux-nav-group__label", children: i.label }),
|
|
150
|
+
i.items.map((o) => (
|
|
151
151
|
// NavLink would mark a parent section current on its children's
|
|
152
152
|
// routes; follow matchActiveItem instead so only the deepest
|
|
153
153
|
// match (or the exact path) stays marked.
|
|
154
154
|
/* @__PURE__ */ e(
|
|
155
155
|
K,
|
|
156
156
|
{
|
|
157
|
-
collapsed:
|
|
158
|
-
end:
|
|
159
|
-
item:
|
|
160
|
-
onNavigate:
|
|
157
|
+
collapsed: r === "collapsed",
|
|
158
|
+
end: o !== c || o.to === u,
|
|
159
|
+
item: o,
|
|
160
|
+
onNavigate: l
|
|
161
161
|
},
|
|
162
|
-
|
|
162
|
+
o.to
|
|
163
163
|
)
|
|
164
164
|
))
|
|
165
|
-
] },
|
|
165
|
+
] }, i.label)) })
|
|
166
166
|
] });
|
|
167
167
|
}
|
|
168
168
|
function H({
|
|
169
169
|
children: a,
|
|
170
170
|
headerSlot: n,
|
|
171
171
|
logo: t,
|
|
172
|
-
nav:
|
|
173
|
-
pageTitle:
|
|
172
|
+
nav: l,
|
|
173
|
+
pageTitle: r,
|
|
174
174
|
title: d
|
|
175
175
|
}) {
|
|
176
|
-
var
|
|
177
|
-
const { pathname: u } =
|
|
178
|
-
return /* @__PURE__ */ e(
|
|
176
|
+
var b;
|
|
177
|
+
const { pathname: u } = v(), { lang: c } = L(), i = I(c), o = D(), _ = r ?? ((b = k(l, u)) == null ? void 0 : b.label);
|
|
178
|
+
return /* @__PURE__ */ e(p.Provider, { delay: 0, children: /* @__PURE__ */ s(
|
|
179
179
|
"div",
|
|
180
180
|
{
|
|
181
181
|
className: "ux-app-layout",
|
|
182
182
|
"data-state": o.open ? "expanded" : "collapsed",
|
|
183
183
|
children: [
|
|
184
184
|
/* @__PURE__ */ e("aside", { className: "ux-sidebar", children: /* @__PURE__ */ e(
|
|
185
|
-
|
|
185
|
+
x,
|
|
186
186
|
{
|
|
187
|
-
copy:
|
|
187
|
+
copy: i,
|
|
188
188
|
logo: t,
|
|
189
|
-
nav:
|
|
189
|
+
nav: l,
|
|
190
190
|
state: o.open ? "expanded" : "collapsed",
|
|
191
191
|
title: d
|
|
192
192
|
}
|
|
193
193
|
) }),
|
|
194
194
|
o.isMobile ? /* @__PURE__ */ e(
|
|
195
|
-
|
|
195
|
+
h.Root,
|
|
196
196
|
{
|
|
197
197
|
open: o.openMobile,
|
|
198
198
|
onOpenChange: o.setOpenMobile,
|
|
199
|
-
children: /* @__PURE__ */
|
|
200
|
-
/* @__PURE__ */ e(
|
|
201
|
-
/* @__PURE__ */
|
|
202
|
-
/* @__PURE__ */ e(
|
|
199
|
+
children: /* @__PURE__ */ s(h.Portal, { children: [
|
|
200
|
+
/* @__PURE__ */ e(h.Backdrop, { className: "ux-drawer-backdrop" }),
|
|
201
|
+
/* @__PURE__ */ s(h.Popup, { className: "ux-drawer", children: [
|
|
202
|
+
/* @__PURE__ */ e(h.Title, { className: "ux-visually-hidden", children: i.navigation }),
|
|
203
203
|
/* @__PURE__ */ e(
|
|
204
204
|
"button",
|
|
205
205
|
{
|
|
206
|
-
"aria-label":
|
|
206
|
+
"aria-label": i.closeNavigation,
|
|
207
207
|
className: "ux-drawer__close",
|
|
208
208
|
type: "button",
|
|
209
209
|
onClick: () => o.setOpenMobile(!1),
|
|
210
|
-
children: /* @__PURE__ */ e(
|
|
210
|
+
children: /* @__PURE__ */ e(E, {})
|
|
211
211
|
}
|
|
212
212
|
),
|
|
213
213
|
/* @__PURE__ */ e(
|
|
214
|
-
|
|
214
|
+
x,
|
|
215
215
|
{
|
|
216
|
-
copy:
|
|
216
|
+
copy: i,
|
|
217
217
|
logo: t,
|
|
218
|
-
nav:
|
|
218
|
+
nav: l,
|
|
219
219
|
onNavigate: () => o.setOpenMobile(!1),
|
|
220
220
|
state: "expanded",
|
|
221
221
|
title: d
|
|
@@ -225,21 +225,21 @@ function H({
|
|
|
225
225
|
] })
|
|
226
226
|
}
|
|
227
227
|
) : null,
|
|
228
|
-
/* @__PURE__ */
|
|
229
|
-
/* @__PURE__ */
|
|
228
|
+
/* @__PURE__ */ s("div", { className: "ux-shell", children: [
|
|
229
|
+
/* @__PURE__ */ s("header", { className: "ux-app-header", children: [
|
|
230
230
|
/* @__PURE__ */ e(
|
|
231
231
|
"button",
|
|
232
232
|
{
|
|
233
233
|
"aria-expanded": o.isMobile ? o.openMobile : o.open,
|
|
234
|
-
"aria-label":
|
|
234
|
+
"aria-label": i.toggleSidebar,
|
|
235
235
|
className: "ux-app-header__trigger",
|
|
236
236
|
type: "button",
|
|
237
237
|
onClick: o.toggleSidebar,
|
|
238
|
-
children: /* @__PURE__ */ e(
|
|
238
|
+
children: /* @__PURE__ */ e(T, {})
|
|
239
239
|
}
|
|
240
240
|
),
|
|
241
241
|
/* @__PURE__ */ e("span", { "aria-hidden": "true", className: "ux-app-header__separator" }),
|
|
242
|
-
/* @__PURE__ */ e("p", { className: "ux-app-header__title", children:
|
|
242
|
+
/* @__PURE__ */ e("p", { className: "ux-app-header__title", children: _ }),
|
|
243
243
|
/* @__PURE__ */ e("div", { className: "ux-app-header__slot", children: n }),
|
|
244
244
|
/* @__PURE__ */ e(S, {})
|
|
245
245
|
] }),
|
package/dist/styles.css
CHANGED
|
@@ -1,11 +1,52 @@
|
|
|
1
1
|
/* @zccz14/ux — unified AppLayout shell. Include this file once from the consuming application. */
|
|
2
2
|
|
|
3
|
+
/*
|
|
4
|
+
* Colour layer. Every shell colour resolves through a --ux-* token, and every
|
|
5
|
+
* --ux-* token prefers the host app's shadcn token over a built-in fallback:
|
|
6
|
+
* light values on :root, dark values on .dark. Both sets exist because the
|
|
7
|
+
* fallback is the only thing left when an app omits part of the shadcn token
|
|
8
|
+
* set — without a dark set, a missing --sidebar would keep the sidebar light
|
|
9
|
+
* in dark mode.
|
|
10
|
+
*/
|
|
11
|
+
:root {
|
|
12
|
+
--ux-background: var(--background, #fff);
|
|
13
|
+
--ux-foreground: var(--foreground, #0f172a);
|
|
14
|
+
--ux-border: var(--border, #e2e8f0);
|
|
15
|
+
--ux-accent: var(--accent, var(--muted, #f1f5f9));
|
|
16
|
+
--ux-ring: var(--ring, #2563eb);
|
|
17
|
+
--ux-sidebar: var(--sidebar, #fafafa);
|
|
18
|
+
--ux-sidebar-foreground: var(--sidebar-foreground, var(--foreground, #0f172a));
|
|
19
|
+
--ux-sidebar-accent: var(--sidebar-accent, var(--accent, #f1f5f9));
|
|
20
|
+
--ux-sidebar-accent-foreground: var(--sidebar-accent-foreground, var(--foreground, #0f172a));
|
|
21
|
+
--ux-sidebar-border: var(--sidebar-border, var(--border, #e2e8f0));
|
|
22
|
+
--ux-sidebar-ring: var(--sidebar-ring, var(--ring, #2563eb));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.dark {
|
|
26
|
+
--ux-background: var(--background, #121212);
|
|
27
|
+
--ux-foreground: var(--foreground, #f2f2f2);
|
|
28
|
+
--ux-border: var(--border, #383838);
|
|
29
|
+
--ux-accent: var(--accent, var(--muted, #303030));
|
|
30
|
+
--ux-ring: var(--ring, #d4d4d4);
|
|
31
|
+
--ux-sidebar: var(--sidebar, #161616);
|
|
32
|
+
--ux-sidebar-foreground: var(--sidebar-foreground, var(--foreground, #f2f2f2));
|
|
33
|
+
--ux-sidebar-accent: var(--sidebar-accent, var(--accent, #303030));
|
|
34
|
+
--ux-sidebar-accent-foreground: var(--sidebar-accent-foreground, var(--foreground, #f2f2f2));
|
|
35
|
+
--ux-sidebar-border: var(--sidebar-border, var(--border, #383838));
|
|
36
|
+
--ux-sidebar-ring: var(--sidebar-ring, var(--ring, #d4d4d4));
|
|
37
|
+
}
|
|
38
|
+
|
|
3
39
|
.ux-app-layout {
|
|
4
40
|
display: flex;
|
|
5
41
|
height: 100svh;
|
|
6
42
|
overflow: hidden;
|
|
7
|
-
color: var(--foreground
|
|
8
|
-
|
|
43
|
+
color: var(--ux-foreground);
|
|
44
|
+
color-scheme: light;
|
|
45
|
+
background: var(--ux-background);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.dark .ux-app-layout {
|
|
49
|
+
color-scheme: dark;
|
|
9
50
|
}
|
|
10
51
|
|
|
11
52
|
/* --- Sidebar --- */
|
|
@@ -16,9 +57,9 @@
|
|
|
16
57
|
flex-direction: column;
|
|
17
58
|
width: 16rem;
|
|
18
59
|
overflow: hidden;
|
|
19
|
-
color: var(--sidebar-foreground
|
|
20
|
-
background: var(--sidebar
|
|
21
|
-
border-right: 1px solid var(--sidebar-border
|
|
60
|
+
color: var(--ux-sidebar-foreground);
|
|
61
|
+
background: var(--ux-sidebar);
|
|
62
|
+
border-right: 1px solid var(--ux-sidebar-border);
|
|
22
63
|
transition: width 200ms linear;
|
|
23
64
|
}
|
|
24
65
|
|
|
@@ -46,7 +87,7 @@
|
|
|
46
87
|
padding-right: 3rem;
|
|
47
88
|
}
|
|
48
89
|
|
|
49
|
-
/* The framework owns the logo display size:
|
|
90
|
+
/* The framework owns the logo display size: the mark renders inside a fixed 1.75rem box. */
|
|
50
91
|
.ux-sidebar__logo {
|
|
51
92
|
display: flex;
|
|
52
93
|
flex: none;
|
|
@@ -105,11 +146,12 @@
|
|
|
105
146
|
font-weight: 500;
|
|
106
147
|
white-space: nowrap;
|
|
107
148
|
opacity: 0.7;
|
|
108
|
-
transition: opacity 200ms linear;
|
|
149
|
+
transition: margin-top 200ms linear, opacity 200ms linear;
|
|
109
150
|
}
|
|
110
151
|
|
|
111
152
|
.ux-app-layout[data-state="collapsed"] .ux-nav-group__label {
|
|
112
|
-
|
|
153
|
+
margin-top: -2rem;
|
|
154
|
+
opacity: 0;
|
|
113
155
|
}
|
|
114
156
|
|
|
115
157
|
.ux-nav-item {
|
|
@@ -134,19 +176,19 @@
|
|
|
134
176
|
}
|
|
135
177
|
|
|
136
178
|
.ux-nav-item:hover {
|
|
137
|
-
color: var(--sidebar-accent-foreground
|
|
138
|
-
background: var(--sidebar-accent
|
|
179
|
+
color: var(--ux-sidebar-accent-foreground);
|
|
180
|
+
background: var(--ux-sidebar-accent);
|
|
139
181
|
}
|
|
140
182
|
|
|
141
183
|
.ux-nav-item:focus-visible {
|
|
142
|
-
outline: 2px solid var(--sidebar-ring
|
|
184
|
+
outline: 2px solid var(--ux-sidebar-ring);
|
|
143
185
|
outline-offset: -2px;
|
|
144
186
|
}
|
|
145
187
|
|
|
146
188
|
.ux-nav-item[aria-current="page"] {
|
|
147
|
-
color: var(--sidebar-accent-foreground
|
|
189
|
+
color: var(--ux-sidebar-accent-foreground);
|
|
148
190
|
font-weight: 500;
|
|
149
|
-
background: var(--sidebar-accent
|
|
191
|
+
background: var(--ux-sidebar-accent);
|
|
150
192
|
}
|
|
151
193
|
|
|
152
194
|
.ux-app-layout[data-state="collapsed"] .ux-nav-item {
|
|
@@ -204,8 +246,8 @@
|
|
|
204
246
|
gap: 0.75rem;
|
|
205
247
|
height: 3.5rem;
|
|
206
248
|
padding: 0 1rem;
|
|
207
|
-
background: var(--background
|
|
208
|
-
border-bottom: 1px solid var(--border
|
|
249
|
+
background: var(--ux-background);
|
|
250
|
+
border-bottom: 1px solid var(--ux-border);
|
|
209
251
|
}
|
|
210
252
|
|
|
211
253
|
.ux-app-header__trigger {
|
|
@@ -227,11 +269,11 @@
|
|
|
227
269
|
}
|
|
228
270
|
|
|
229
271
|
.ux-app-header__trigger:hover {
|
|
230
|
-
background: var(--accent
|
|
272
|
+
background: var(--ux-accent);
|
|
231
273
|
}
|
|
232
274
|
|
|
233
275
|
.ux-app-header__trigger:focus-visible {
|
|
234
|
-
outline: 2px solid var(--ring
|
|
276
|
+
outline: 2px solid var(--ux-ring);
|
|
235
277
|
outline-offset: 2px;
|
|
236
278
|
}
|
|
237
279
|
|
|
@@ -244,7 +286,7 @@
|
|
|
244
286
|
flex: none;
|
|
245
287
|
width: 1px;
|
|
246
288
|
height: 1.25rem;
|
|
247
|
-
background: var(--border
|
|
289
|
+
background: var(--ux-border);
|
|
248
290
|
}
|
|
249
291
|
|
|
250
292
|
.ux-app-header__title {
|
|
@@ -302,13 +344,18 @@
|
|
|
302
344
|
width: 18rem;
|
|
303
345
|
max-width: calc(100vw - 3rem);
|
|
304
346
|
overflow: hidden;
|
|
305
|
-
color: var(--sidebar-foreground
|
|
306
|
-
|
|
307
|
-
|
|
347
|
+
color: var(--ux-sidebar-foreground);
|
|
348
|
+
color-scheme: light;
|
|
349
|
+
background: var(--ux-sidebar);
|
|
350
|
+
border-right: 1px solid var(--ux-sidebar-border);
|
|
308
351
|
outline: none;
|
|
309
352
|
transition: transform 200ms ease-out, opacity 200ms ease-out;
|
|
310
353
|
}
|
|
311
354
|
|
|
355
|
+
.dark .ux-drawer {
|
|
356
|
+
color-scheme: dark;
|
|
357
|
+
}
|
|
358
|
+
|
|
312
359
|
.ux-drawer[data-starting-style],
|
|
313
360
|
.ux-drawer[data-ending-style] {
|
|
314
361
|
opacity: 0;
|
|
@@ -335,11 +382,11 @@
|
|
|
335
382
|
}
|
|
336
383
|
|
|
337
384
|
.ux-drawer__close:hover {
|
|
338
|
-
background: var(--sidebar-accent
|
|
385
|
+
background: var(--ux-sidebar-accent);
|
|
339
386
|
}
|
|
340
387
|
|
|
341
388
|
.ux-drawer__close:focus-visible {
|
|
342
|
-
outline: 2px solid var(--sidebar-ring
|
|
389
|
+
outline: 2px solid var(--ux-sidebar-ring);
|
|
343
390
|
outline-offset: 2px;
|
|
344
391
|
}
|
|
345
392
|
|
|
@@ -360,12 +407,12 @@
|
|
|
360
407
|
align-items: center;
|
|
361
408
|
max-width: 16rem;
|
|
362
409
|
padding: 0.375rem 0.75rem;
|
|
363
|
-
color: var(--background
|
|
410
|
+
color: var(--ux-background);
|
|
364
411
|
font-size: 0.75rem;
|
|
365
412
|
font-weight: 500;
|
|
366
413
|
line-height: 1.25;
|
|
367
414
|
white-space: nowrap;
|
|
368
|
-
background: var(--foreground
|
|
415
|
+
background: var(--ux-foreground);
|
|
369
416
|
border-radius: 0.375rem;
|
|
370
417
|
box-shadow: 0 4px 12px rgb(0 0 0 / 0.12);
|
|
371
418
|
transition: opacity 120ms ease-out, transform 120ms ease-out;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
export type AppLogo = {
|
|
3
|
-
light: ReactNode;
|
|
4
|
-
dark: ReactNode;
|
|
5
|
-
};
|
|
6
2
|
export type AppNavItem = {
|
|
7
3
|
to: string;
|
|
8
4
|
label: string;
|
|
@@ -13,7 +9,7 @@ export type AppNavGroup = {
|
|
|
13
9
|
items: AppNavItem[];
|
|
14
10
|
};
|
|
15
11
|
export type AppLayoutProps = {
|
|
16
|
-
logo:
|
|
12
|
+
logo: ReactNode;
|
|
17
13
|
title: string;
|
|
18
14
|
nav: AppNavGroup[];
|
|
19
15
|
headerSlot?: ReactNode;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zccz14/ux",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Unified AppLayout shell for the NTNL app family: one animated icon sidebar, a fixed-size
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Unified AppLayout shell for the NTNL app family: one animated icon sidebar, a fixed-size logo box, and the Linkit-integrated AppHeader.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|