@zccz14/ux 0.1.5 → 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 +69 -23
- 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;
|
|
@@ -135,19 +176,19 @@
|
|
|
135
176
|
}
|
|
136
177
|
|
|
137
178
|
.ux-nav-item:hover {
|
|
138
|
-
color: var(--sidebar-accent-foreground
|
|
139
|
-
background: var(--sidebar-accent
|
|
179
|
+
color: var(--ux-sidebar-accent-foreground);
|
|
180
|
+
background: var(--ux-sidebar-accent);
|
|
140
181
|
}
|
|
141
182
|
|
|
142
183
|
.ux-nav-item:focus-visible {
|
|
143
|
-
outline: 2px solid var(--sidebar-ring
|
|
184
|
+
outline: 2px solid var(--ux-sidebar-ring);
|
|
144
185
|
outline-offset: -2px;
|
|
145
186
|
}
|
|
146
187
|
|
|
147
188
|
.ux-nav-item[aria-current="page"] {
|
|
148
|
-
color: var(--sidebar-accent-foreground
|
|
189
|
+
color: var(--ux-sidebar-accent-foreground);
|
|
149
190
|
font-weight: 500;
|
|
150
|
-
background: var(--sidebar-accent
|
|
191
|
+
background: var(--ux-sidebar-accent);
|
|
151
192
|
}
|
|
152
193
|
|
|
153
194
|
.ux-app-layout[data-state="collapsed"] .ux-nav-item {
|
|
@@ -205,8 +246,8 @@
|
|
|
205
246
|
gap: 0.75rem;
|
|
206
247
|
height: 3.5rem;
|
|
207
248
|
padding: 0 1rem;
|
|
208
|
-
background: var(--background
|
|
209
|
-
border-bottom: 1px solid var(--border
|
|
249
|
+
background: var(--ux-background);
|
|
250
|
+
border-bottom: 1px solid var(--ux-border);
|
|
210
251
|
}
|
|
211
252
|
|
|
212
253
|
.ux-app-header__trigger {
|
|
@@ -228,11 +269,11 @@
|
|
|
228
269
|
}
|
|
229
270
|
|
|
230
271
|
.ux-app-header__trigger:hover {
|
|
231
|
-
background: var(--accent
|
|
272
|
+
background: var(--ux-accent);
|
|
232
273
|
}
|
|
233
274
|
|
|
234
275
|
.ux-app-header__trigger:focus-visible {
|
|
235
|
-
outline: 2px solid var(--ring
|
|
276
|
+
outline: 2px solid var(--ux-ring);
|
|
236
277
|
outline-offset: 2px;
|
|
237
278
|
}
|
|
238
279
|
|
|
@@ -245,7 +286,7 @@
|
|
|
245
286
|
flex: none;
|
|
246
287
|
width: 1px;
|
|
247
288
|
height: 1.25rem;
|
|
248
|
-
background: var(--border
|
|
289
|
+
background: var(--ux-border);
|
|
249
290
|
}
|
|
250
291
|
|
|
251
292
|
.ux-app-header__title {
|
|
@@ -303,13 +344,18 @@
|
|
|
303
344
|
width: 18rem;
|
|
304
345
|
max-width: calc(100vw - 3rem);
|
|
305
346
|
overflow: hidden;
|
|
306
|
-
color: var(--sidebar-foreground
|
|
307
|
-
|
|
308
|
-
|
|
347
|
+
color: var(--ux-sidebar-foreground);
|
|
348
|
+
color-scheme: light;
|
|
349
|
+
background: var(--ux-sidebar);
|
|
350
|
+
border-right: 1px solid var(--ux-sidebar-border);
|
|
309
351
|
outline: none;
|
|
310
352
|
transition: transform 200ms ease-out, opacity 200ms ease-out;
|
|
311
353
|
}
|
|
312
354
|
|
|
355
|
+
.dark .ux-drawer {
|
|
356
|
+
color-scheme: dark;
|
|
357
|
+
}
|
|
358
|
+
|
|
313
359
|
.ux-drawer[data-starting-style],
|
|
314
360
|
.ux-drawer[data-ending-style] {
|
|
315
361
|
opacity: 0;
|
|
@@ -336,11 +382,11 @@
|
|
|
336
382
|
}
|
|
337
383
|
|
|
338
384
|
.ux-drawer__close:hover {
|
|
339
|
-
background: var(--sidebar-accent
|
|
385
|
+
background: var(--ux-sidebar-accent);
|
|
340
386
|
}
|
|
341
387
|
|
|
342
388
|
.ux-drawer__close:focus-visible {
|
|
343
|
-
outline: 2px solid var(--sidebar-ring
|
|
389
|
+
outline: 2px solid var(--ux-sidebar-ring);
|
|
344
390
|
outline-offset: 2px;
|
|
345
391
|
}
|
|
346
392
|
|
|
@@ -361,12 +407,12 @@
|
|
|
361
407
|
align-items: center;
|
|
362
408
|
max-width: 16rem;
|
|
363
409
|
padding: 0.375rem 0.75rem;
|
|
364
|
-
color: var(--background
|
|
410
|
+
color: var(--ux-background);
|
|
365
411
|
font-size: 0.75rem;
|
|
366
412
|
font-weight: 500;
|
|
367
413
|
line-height: 1.25;
|
|
368
414
|
white-space: nowrap;
|
|
369
|
-
background: var(--foreground
|
|
415
|
+
background: var(--ux-foreground);
|
|
370
416
|
border-radius: 0.375rem;
|
|
371
417
|
box-shadow: 0 4px 12px rgb(0 0 0 / 0.12);
|
|
372
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",
|