@naeil/ui 0.4.0 → 0.6.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/CHANGELOG.md +19 -1
- package/DESIGN.md +24 -1
- package/README.md +25 -9
- package/THIRD_PARTY_NOTICES.md +4 -0
- package/dist/chunk-CE73AEJZ.js +667 -0
- package/dist/{chunk-QKVN6Q6C.js → chunk-QSTGUEEI.js} +60 -175
- package/dist/chunk-VL7FS4QM.js +1512 -0
- package/dist/components/index.d.ts +19 -5
- package/dist/components/index.js +135 -1618
- package/dist/components/naeil-header.d.ts +54 -0
- package/dist/components/naeil-header.js +14 -0
- package/dist/components/ui/index.d.ts +2 -0
- package/dist/components/ui/index.js +40 -179
- package/dist/navigation-CL10a6Eb.d.ts +48 -0
- package/dist/site-footer-B67meCDv.d.ts +21 -0
- package/dist/source/components/footer.d.ts +13 -4
- package/dist/source/components/footer.js +74 -71
- package/dist/source/components/index.d.ts +5 -3
- package/dist/source/components/locale-switcher.js +1 -1
- package/dist/source/components/naeil-header-content.d.ts +171 -0
- package/dist/source/components/naeil-header-content.js +176 -0
- package/dist/source/components/naeil-header.d.ts +52 -0
- package/dist/source/components/naeil-header.js +196 -0
- package/dist/source/components/nav.d.ts +10 -2
- package/dist/source/components/nav.js +51 -347
- package/dist/source/components/ui/index.d.ts +2 -0
- package/dist/source/components/ui/index.js +4 -0
- package/dist/source/components/ui/navigation.d.ts +48 -0
- package/dist/source/components/ui/navigation.js +347 -0
- package/dist/source/components/ui/site-footer.d.ts +21 -0
- package/dist/source/components/ui/site-footer.js +93 -0
- package/docs/components/README.md +8 -1
- package/docs/components/application-layout.md +102 -0
- package/docs/components/common.md +2 -2
- package/docs/components/naeil-header.md +50 -0
- package/docs/components/navigation.md +57 -0
- package/docs/design/public-ui-support.md +14 -1
- package/docs/design/v2-migration.md +59 -26
- package/docs/package-release.md +40 -14
- package/package.json +13 -1
- package/src/styles/components.css +292 -0
- package/src/styles/theme.css +7 -1
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defProps = Object.defineProperties;
|
|
4
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
5
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
8
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
9
|
+
var __spreadValues = (a, b) => {
|
|
10
|
+
for (var prop in b || (b = {}))
|
|
11
|
+
if (__hasOwnProp.call(b, prop))
|
|
12
|
+
__defNormalProp(a, prop, b[prop]);
|
|
13
|
+
if (__getOwnPropSymbols)
|
|
14
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
15
|
+
if (__propIsEnum.call(b, prop))
|
|
16
|
+
__defNormalProp(a, prop, b[prop]);
|
|
17
|
+
}
|
|
18
|
+
return a;
|
|
19
|
+
};
|
|
20
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
21
|
+
var __objRest = (source, exclude) => {
|
|
22
|
+
var target = {};
|
|
23
|
+
for (var prop in source)
|
|
24
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
25
|
+
target[prop] = source[prop];
|
|
26
|
+
if (source != null && __getOwnPropSymbols)
|
|
27
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
28
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
29
|
+
target[prop] = source[prop];
|
|
30
|
+
}
|
|
31
|
+
return target;
|
|
32
|
+
};
|
|
33
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
34
|
+
import * as React from "react";
|
|
35
|
+
import { Dialog, Popover } from "radix-ui";
|
|
36
|
+
import { Check, ChevronDown, Menu, X } from "lucide-react";
|
|
37
|
+
import { cn } from "../../lib/utils.js";
|
|
38
|
+
const NativeLink = React.forwardRef(
|
|
39
|
+
function NativeLink2(props, ref) {
|
|
40
|
+
return /* @__PURE__ */ jsx("a", __spreadValues({ ref }, props));
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
function DesktopGroup({
|
|
44
|
+
item,
|
|
45
|
+
active,
|
|
46
|
+
open,
|
|
47
|
+
anotherGroupOpen,
|
|
48
|
+
onOpenChange,
|
|
49
|
+
children
|
|
50
|
+
}) {
|
|
51
|
+
var _a;
|
|
52
|
+
const hoverOpened = React.useRef(false);
|
|
53
|
+
const closeTimer = React.useRef(
|
|
54
|
+
void 0
|
|
55
|
+
);
|
|
56
|
+
const panel = React.useRef(null);
|
|
57
|
+
const trigger = React.useRef(null);
|
|
58
|
+
const pointerType = React.useRef("");
|
|
59
|
+
const cancelClose = React.useCallback(() => {
|
|
60
|
+
clearTimeout(closeTimer.current);
|
|
61
|
+
}, []);
|
|
62
|
+
React.useEffect(() => {
|
|
63
|
+
return cancelClose;
|
|
64
|
+
}, [open, cancelClose]);
|
|
65
|
+
const leave = (event) => {
|
|
66
|
+
if (event.pointerType !== "mouse") return;
|
|
67
|
+
cancelClose();
|
|
68
|
+
const duration = getComputedStyle(event.currentTarget).getPropertyValue("--ui-motion-standard").trim();
|
|
69
|
+
closeTimer.current = setTimeout(
|
|
70
|
+
() => {
|
|
71
|
+
if (hoverOpened.current) onOpenChange(false);
|
|
72
|
+
},
|
|
73
|
+
parseFloat(duration) * (duration.endsWith("ms") ? 1 : 1e3)
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
return /* @__PURE__ */ jsxs(
|
|
77
|
+
Popover.Root,
|
|
78
|
+
{
|
|
79
|
+
open,
|
|
80
|
+
onOpenChange: (next) => {
|
|
81
|
+
cancelClose();
|
|
82
|
+
if (next) hoverOpened.current = false;
|
|
83
|
+
onOpenChange(next);
|
|
84
|
+
},
|
|
85
|
+
children: [
|
|
86
|
+
/* @__PURE__ */ jsxs(
|
|
87
|
+
Popover.Trigger,
|
|
88
|
+
{
|
|
89
|
+
ref: trigger,
|
|
90
|
+
className: "ui-navigation-link ui-focus",
|
|
91
|
+
"data-active": active || void 0,
|
|
92
|
+
onPointerEnter: (event) => {
|
|
93
|
+
if (event.pointerType !== "mouse") return;
|
|
94
|
+
cancelClose();
|
|
95
|
+
if (!open) {
|
|
96
|
+
hoverOpened.current = true;
|
|
97
|
+
onOpenChange(true);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
onPointerLeave: leave,
|
|
101
|
+
onPointerDown: (event) => {
|
|
102
|
+
pointerType.current = event.pointerType;
|
|
103
|
+
},
|
|
104
|
+
onClick: (event) => {
|
|
105
|
+
var _a2, _b;
|
|
106
|
+
if (event.detail > 0 && pointerType.current === "mouse") {
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
cancelClose();
|
|
109
|
+
hoverOpened.current = true;
|
|
110
|
+
onOpenChange(true);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (open && hoverOpened.current) {
|
|
114
|
+
event.preventDefault();
|
|
115
|
+
hoverOpened.current = false;
|
|
116
|
+
cancelClose();
|
|
117
|
+
(_b = (_a2 = panel.current) == null ? void 0 : _a2.querySelector("a[href]")) == null ? void 0 : _b.focus();
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
children: [
|
|
121
|
+
item.label,
|
|
122
|
+
/* @__PURE__ */ jsx(ChevronDown, { size: 16, "aria-hidden": "true" })
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
),
|
|
126
|
+
/* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(
|
|
127
|
+
Popover.Content,
|
|
128
|
+
{
|
|
129
|
+
ref: panel,
|
|
130
|
+
"data-slot": "navigation-popover",
|
|
131
|
+
className: "ui-navigation-popover ui-popup",
|
|
132
|
+
"data-columns": (_a = item.columns) != null ? _a : 1,
|
|
133
|
+
align: "start",
|
|
134
|
+
sideOffset: 8,
|
|
135
|
+
"aria-label": item.label,
|
|
136
|
+
onPointerEnter: cancelClose,
|
|
137
|
+
onPointerLeave: leave,
|
|
138
|
+
onFocusCapture: () => {
|
|
139
|
+
hoverOpened.current = false;
|
|
140
|
+
cancelClose();
|
|
141
|
+
},
|
|
142
|
+
onOpenAutoFocus: (event) => {
|
|
143
|
+
if (hoverOpened.current) event.preventDefault();
|
|
144
|
+
},
|
|
145
|
+
onCloseAutoFocus: (event) => {
|
|
146
|
+
if (hoverOpened.current || anotherGroupOpen) event.preventDefault();
|
|
147
|
+
},
|
|
148
|
+
onInteractOutside: (event) => {
|
|
149
|
+
var _a2;
|
|
150
|
+
if ((_a2 = trigger.current) == null ? void 0 : _a2.contains(event.target))
|
|
151
|
+
event.preventDefault();
|
|
152
|
+
},
|
|
153
|
+
children: /* @__PURE__ */ jsx("nav", { "aria-label": item.label, className: "ui-navigation-menu-grid", children })
|
|
154
|
+
}
|
|
155
|
+
) })
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
function Navigation(_a) {
|
|
161
|
+
var _b = _a, {
|
|
162
|
+
brand,
|
|
163
|
+
items,
|
|
164
|
+
labels,
|
|
165
|
+
Link = NativeLink,
|
|
166
|
+
currentHref,
|
|
167
|
+
toolbar,
|
|
168
|
+
mobileToolbar,
|
|
169
|
+
mobileFooter,
|
|
170
|
+
containerClassName,
|
|
171
|
+
onNavigate,
|
|
172
|
+
className
|
|
173
|
+
} = _b, props = __objRest(_b, [
|
|
174
|
+
"brand",
|
|
175
|
+
"items",
|
|
176
|
+
"labels",
|
|
177
|
+
"Link",
|
|
178
|
+
"currentHref",
|
|
179
|
+
"toolbar",
|
|
180
|
+
"mobileToolbar",
|
|
181
|
+
"mobileFooter",
|
|
182
|
+
"containerClassName",
|
|
183
|
+
"onNavigate",
|
|
184
|
+
"className"
|
|
185
|
+
]);
|
|
186
|
+
const [mobileOpen, setMobileOpen] = React.useState(false);
|
|
187
|
+
const [openGroup, setOpenGroup] = React.useState(null);
|
|
188
|
+
const trigger = React.useRef(null);
|
|
189
|
+
const desktop = React.useRef(null);
|
|
190
|
+
React.useEffect(() => {
|
|
191
|
+
setMobileOpen(false);
|
|
192
|
+
setOpenGroup(null);
|
|
193
|
+
}, [currentHref]);
|
|
194
|
+
React.useEffect(() => {
|
|
195
|
+
const element = desktop.current;
|
|
196
|
+
if (!element || typeof ResizeObserver === "undefined") return;
|
|
197
|
+
const observer = new ResizeObserver(() => {
|
|
198
|
+
if (element.getBoundingClientRect().width > 0) setMobileOpen(false);
|
|
199
|
+
else setOpenGroup(null);
|
|
200
|
+
});
|
|
201
|
+
observer.observe(element);
|
|
202
|
+
return () => observer.disconnect();
|
|
203
|
+
}, []);
|
|
204
|
+
const active = (item) => {
|
|
205
|
+
var _a2;
|
|
206
|
+
return (_a2 = item.active) != null ? _a2 : !item.external && item.href === currentHref;
|
|
207
|
+
};
|
|
208
|
+
function link(item, close, entry = false) {
|
|
209
|
+
const Component = item.external ? NativeLink : Link;
|
|
210
|
+
return /* @__PURE__ */ jsxs(
|
|
211
|
+
Component,
|
|
212
|
+
{
|
|
213
|
+
href: item.href,
|
|
214
|
+
target: item.target,
|
|
215
|
+
rel: item.target === "_blank" ? "noopener noreferrer" : void 0,
|
|
216
|
+
"aria-current": active(item) ? "page" : void 0,
|
|
217
|
+
className: cn(
|
|
218
|
+
"ui-navigation-link ui-focus",
|
|
219
|
+
entry && "ui-navigation-entry"
|
|
220
|
+
),
|
|
221
|
+
onClick: (event) => {
|
|
222
|
+
onNavigate == null ? void 0 : onNavigate(event, item);
|
|
223
|
+
if (event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey)
|
|
224
|
+
close == null ? void 0 : close();
|
|
225
|
+
},
|
|
226
|
+
children: [
|
|
227
|
+
item.icon && /* @__PURE__ */ jsx("span", { className: "ui-navigation-icon", "aria-hidden": "true", children: item.icon }),
|
|
228
|
+
/* @__PURE__ */ jsxs("span", { className: "ui-navigation-copy", children: [
|
|
229
|
+
/* @__PURE__ */ jsxs("span", { className: "ui-navigation-title", children: [
|
|
230
|
+
/* @__PURE__ */ jsx("span", { children: item.label }),
|
|
231
|
+
item.badge,
|
|
232
|
+
entry && active(item) && /* @__PURE__ */ jsx(Check, { size: 16, className: "shrink-0", "aria-hidden": "true" })
|
|
233
|
+
] }),
|
|
234
|
+
item.description && /* @__PURE__ */ jsx("span", { className: "ui-navigation-description", children: item.description })
|
|
235
|
+
] })
|
|
236
|
+
]
|
|
237
|
+
},
|
|
238
|
+
item.id
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
return /* @__PURE__ */ jsx(
|
|
242
|
+
"header",
|
|
243
|
+
__spreadProps(__spreadValues({
|
|
244
|
+
"data-slot": "navigation",
|
|
245
|
+
className: cn("ui-navigation", className)
|
|
246
|
+
}, props), {
|
|
247
|
+
children: /* @__PURE__ */ jsx("div", { className: cn("ui-navigation-frame", containerClassName), children: /* @__PURE__ */ jsxs("div", { className: "ui-navigation-row", children: [
|
|
248
|
+
/* @__PURE__ */ jsx("div", { className: "ui-navigation-brand", children: brand }),
|
|
249
|
+
/* @__PURE__ */ jsx(
|
|
250
|
+
"nav",
|
|
251
|
+
{
|
|
252
|
+
ref: desktop,
|
|
253
|
+
"aria-label": labels.navigation,
|
|
254
|
+
className: "ui-navigation-desktop",
|
|
255
|
+
children: items.map(
|
|
256
|
+
(item) => "items" in item ? /* @__PURE__ */ jsx(
|
|
257
|
+
DesktopGroup,
|
|
258
|
+
{
|
|
259
|
+
item,
|
|
260
|
+
active: item.items.some(active),
|
|
261
|
+
open: openGroup === item.id,
|
|
262
|
+
anotherGroupOpen: openGroup !== null && openGroup !== item.id,
|
|
263
|
+
onOpenChange: (open) => setOpenGroup(
|
|
264
|
+
(current) => open ? item.id : current === item.id ? null : current
|
|
265
|
+
),
|
|
266
|
+
children: item.items.map(
|
|
267
|
+
(child) => link(child, () => setOpenGroup(null), true)
|
|
268
|
+
)
|
|
269
|
+
},
|
|
270
|
+
item.id
|
|
271
|
+
) : link(item)
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
),
|
|
275
|
+
/* @__PURE__ */ jsx("div", { className: "ui-navigation-toolbar", children: toolbar }),
|
|
276
|
+
/* @__PURE__ */ jsxs(Dialog.Root, { open: mobileOpen, onOpenChange: setMobileOpen, children: [
|
|
277
|
+
/* @__PURE__ */ jsx(
|
|
278
|
+
Dialog.Trigger,
|
|
279
|
+
{
|
|
280
|
+
ref: trigger,
|
|
281
|
+
className: "ui-navigation-toggle ui-navigation-link ui-focus",
|
|
282
|
+
"aria-label": labels.menu,
|
|
283
|
+
children: /* @__PURE__ */ jsx(Menu, { "aria-hidden": "true", size: 20 })
|
|
284
|
+
}
|
|
285
|
+
),
|
|
286
|
+
/* @__PURE__ */ jsxs(Dialog.Portal, { children: [
|
|
287
|
+
/* @__PURE__ */ jsx(
|
|
288
|
+
Dialog.Overlay,
|
|
289
|
+
{
|
|
290
|
+
"data-slot": "navigation-overlay",
|
|
291
|
+
className: "ui-navigation-overlay ui-popup"
|
|
292
|
+
}
|
|
293
|
+
),
|
|
294
|
+
/* @__PURE__ */ jsxs(
|
|
295
|
+
Dialog.Content,
|
|
296
|
+
{
|
|
297
|
+
"data-slot": "navigation-sheet",
|
|
298
|
+
className: "ui-navigation-sheet ui-popup",
|
|
299
|
+
"aria-describedby": void 0,
|
|
300
|
+
onCloseAutoFocus: (event) => {
|
|
301
|
+
var _a2, _b2;
|
|
302
|
+
if (trigger.current && trigger.current.getBoundingClientRect().width === 0) {
|
|
303
|
+
event.preventDefault();
|
|
304
|
+
(_b2 = (_a2 = desktop.current) == null ? void 0 : _a2.querySelector("button, a")) == null ? void 0 : _b2.focus();
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
children: [
|
|
308
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-navigation-sheet-heading", children: [
|
|
309
|
+
/* @__PURE__ */ jsx(Dialog.Title, { className: "text-xl font-semibold", children: labels.menu }),
|
|
310
|
+
/* @__PURE__ */ jsx(
|
|
311
|
+
Dialog.Close,
|
|
312
|
+
{
|
|
313
|
+
className: "ui-navigation-link ui-focus",
|
|
314
|
+
"aria-label": labels.close,
|
|
315
|
+
children: /* @__PURE__ */ jsx(X, { size: 20, "aria-hidden": "true" })
|
|
316
|
+
}
|
|
317
|
+
)
|
|
318
|
+
] }),
|
|
319
|
+
/* @__PURE__ */ jsx(
|
|
320
|
+
"nav",
|
|
321
|
+
{
|
|
322
|
+
"aria-label": labels.navigation,
|
|
323
|
+
className: "ui-navigation-mobile-links",
|
|
324
|
+
children: items.map(
|
|
325
|
+
(item) => "items" in item ? /* @__PURE__ */ jsxs("section", { "aria-label": item.label, children: [
|
|
326
|
+
/* @__PURE__ */ jsx("h3", { className: "ui-navigation-group-label", children: item.label }),
|
|
327
|
+
item.items.map(
|
|
328
|
+
(child) => link(child, () => setMobileOpen(false), true)
|
|
329
|
+
)
|
|
330
|
+
] }, item.id) : link(item, () => setMobileOpen(false))
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
),
|
|
334
|
+
(mobileToolbar != null ? mobileToolbar : toolbar) && /* @__PURE__ */ jsx("div", { className: "ui-navigation-mobile-toolbar", children: mobileToolbar != null ? mobileToolbar : toolbar }),
|
|
335
|
+
mobileFooter && /* @__PURE__ */ jsx("div", { className: "ui-navigation-mobile-footer", children: mobileFooter })
|
|
336
|
+
]
|
|
337
|
+
}
|
|
338
|
+
)
|
|
339
|
+
] })
|
|
340
|
+
] })
|
|
341
|
+
] }) })
|
|
342
|
+
})
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
export {
|
|
346
|
+
Navigation
|
|
347
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { NavigationLink, NavigationLinkComponent } from './navigation.js';
|
|
4
|
+
|
|
5
|
+
interface SiteFooterProps extends react.ComponentPropsWithoutRef<"footer"> {
|
|
6
|
+
brand?: react.ReactNode;
|
|
7
|
+
description?: react.ReactNode;
|
|
8
|
+
groups?: Array<{
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
links: NavigationLink[];
|
|
12
|
+
}>;
|
|
13
|
+
Link?: NavigationLinkComponent;
|
|
14
|
+
meta?: react.ReactNode;
|
|
15
|
+
containerClassName?: string;
|
|
16
|
+
variant?: "full" | "compact";
|
|
17
|
+
}
|
|
18
|
+
/** Product content and translations belong to the consumer, including legal copy. */
|
|
19
|
+
declare function SiteFooter({ brand, description, groups, Link, meta, children, containerClassName, className, variant, ...props }: SiteFooterProps): react_jsx_runtime.JSX.Element;
|
|
20
|
+
|
|
21
|
+
export { SiteFooter, type SiteFooterProps };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
var __objRest = (source, exclude) => {
|
|
21
|
+
var target = {};
|
|
22
|
+
for (var prop in source)
|
|
23
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
24
|
+
target[prop] = source[prop];
|
|
25
|
+
if (source != null && __getOwnPropSymbols)
|
|
26
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
27
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
28
|
+
target[prop] = source[prop];
|
|
29
|
+
}
|
|
30
|
+
return target;
|
|
31
|
+
};
|
|
32
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
33
|
+
import { cn } from "../../lib/utils.js";
|
|
34
|
+
function SiteFooter(_a) {
|
|
35
|
+
var _b = _a, {
|
|
36
|
+
brand,
|
|
37
|
+
description,
|
|
38
|
+
groups = [],
|
|
39
|
+
Link,
|
|
40
|
+
meta,
|
|
41
|
+
children,
|
|
42
|
+
containerClassName,
|
|
43
|
+
className,
|
|
44
|
+
variant = "full"
|
|
45
|
+
} = _b, props = __objRest(_b, [
|
|
46
|
+
"brand",
|
|
47
|
+
"description",
|
|
48
|
+
"groups",
|
|
49
|
+
"Link",
|
|
50
|
+
"meta",
|
|
51
|
+
"children",
|
|
52
|
+
"containerClassName",
|
|
53
|
+
"className",
|
|
54
|
+
"variant"
|
|
55
|
+
]);
|
|
56
|
+
return /* @__PURE__ */ jsx(
|
|
57
|
+
"footer",
|
|
58
|
+
__spreadProps(__spreadValues({
|
|
59
|
+
"data-slot": "site-footer",
|
|
60
|
+
"data-variant": variant,
|
|
61
|
+
className: cn("ui-site-footer", className)
|
|
62
|
+
}, props), {
|
|
63
|
+
children: /* @__PURE__ */ jsxs("div", { className: cn("ui-site-footer-frame", containerClassName), children: [
|
|
64
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-site-footer-brand", children: [
|
|
65
|
+
brand,
|
|
66
|
+
description && /* @__PURE__ */ jsx("div", { className: "ui-site-footer-description", children: description }),
|
|
67
|
+
children
|
|
68
|
+
] }),
|
|
69
|
+
groups.map((group) => /* @__PURE__ */ jsxs("nav", { "aria-label": group.label, children: [
|
|
70
|
+
/* @__PURE__ */ jsx("h2", { className: "ui-site-footer-label", children: group.label }),
|
|
71
|
+
group.links.map((item) => {
|
|
72
|
+
const Component = !item.external && Link ? Link : "a";
|
|
73
|
+
return /* @__PURE__ */ jsx(
|
|
74
|
+
Component,
|
|
75
|
+
{
|
|
76
|
+
href: item.href,
|
|
77
|
+
target: item.target,
|
|
78
|
+
rel: item.target === "_blank" ? "noopener noreferrer" : void 0,
|
|
79
|
+
className: "ui-site-footer-link ui-focus",
|
|
80
|
+
children: item.label
|
|
81
|
+
},
|
|
82
|
+
item.id
|
|
83
|
+
);
|
|
84
|
+
})
|
|
85
|
+
] }, group.id)),
|
|
86
|
+
meta && /* @__PURE__ */ jsx("div", { className: "ui-site-footer-meta", children: meta })
|
|
87
|
+
] })
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
SiteFooter
|
|
93
|
+
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Component usage guides
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
These guides target the **@naeil/ui 0.6.0**; release identities and publication evidence are recorded in the [release record](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.6.0). [Setup, themes, fonts and shared responsibilities](common.md). Run `pnpm storybook` for the current 17 Docs pages (15 UI and 2 Brand) and choose Docs or Usage. The [hosted Storybook](https://naeil-dev.github.io/naeil-ui/) was last verified for [0.5.0 on 2026-10-15](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.5.0); each subsequent deployment is recorded separately. Select chooses a value; DropdownMenu chooses an action.
|
|
4
|
+
|
|
5
|
+
Start application layouts with the [shared navigation/footer selection and frame guide](application-layout.md). It includes a starter, the 1600px frame contract and instructions to place in consumer repositories so agents reuse the shared implementation.
|
|
4
6
|
|
|
5
7
|
| Family | Guide | Runnable story |
|
|
6
8
|
| --- | --- | --- |
|
|
@@ -17,5 +19,10 @@ Published `@naeil/ui@0.3.0` (2026-10-03; [release](https://github.com/naeil-dev/
|
|
|
17
19
|
| Checkbox | [Checkbox](./checkbox.md) | UI / Checkbox / Usage |
|
|
18
20
|
| Tabs | [Tabs](./tabs.md) | UI / Tabs / Usage |
|
|
19
21
|
| Textarea | [Textarea](./textarea.md) | UI / Textarea / Usage |
|
|
22
|
+
| Navigation | [Navigation](./navigation.md) | UI / Navigation / Usage |
|
|
23
|
+
| SiteFooter | [SiteFooter](./navigation.md) | UI / SiteFooter / Usage |
|
|
24
|
+
| NaeilHeader | [NaeilHeader](./naeil-header.md) | Brand / NaeilHeader / Usage, JapaneseApplication |
|
|
20
25
|
|
|
21
26
|
[Field, native radio, Select and action composition](./composition.md) explains consumer state and semantic choices. [Demand decision](../design/component-demand.md) records why these two families were added.
|
|
27
|
+
|
|
28
|
+
Use opt-in `@naeil/ui/naeil-header` for the actual naeil.dev branded header, with official artwork URL references and consumer-owned routing, theme provider and authentication. Use generic `Navigation` from `/ui` for different product menus. [Navigation / SiteFooter](navigation.md) also documents the compatibility adapters.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Application frame and shared navigation/footer
|
|
2
|
+
|
|
3
|
+
This guide targets the **@naeil/ui 0.6.0**, including the width update approved on 2026-10-16. Use the matching registry release or tested local archive. See [consumer setup](../design/v2-migration.md) for CSS, Tailwind sources and fonts.
|
|
4
|
+
|
|
5
|
+
## Choose the shared composition first
|
|
6
|
+
|
|
7
|
+
| Application need | Use |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| The actual naeil.dev brand, project catalog and controls | `NaeilHeader` from `@naeil/ui/naeil-header` |
|
|
10
|
+
| A product's own menu | `Navigation` from `@naeil/ui/ui` |
|
|
11
|
+
| A full or compact application footer | `SiteFooter` from `@naeil/ui/ui` |
|
|
12
|
+
| An existing Next/next-intl consumer | Keep its `Nav` / `Footer` adapters |
|
|
13
|
+
|
|
14
|
+
Choose one header per application frame. Supply menu data, links, route guards, authentication and supported locale state through props/slots. Local adapters are appropriate; copies of the menu, responsive sheet, controls or footer implementation are not. Extend the shared component when a reusable behavior is missing.
|
|
15
|
+
|
|
16
|
+
## Width contract
|
|
17
|
+
|
|
18
|
+
| Region | Default | CSS contract |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| Header, main, footer outer frame | Maximum 1600px, including gutters | `--ui-width-frame` |
|
|
21
|
+
| Inline gutters | `clamp(16px, 2.4vw, 40px)` | `--ui-frame-gutter` |
|
|
22
|
+
| Reading / settings | Maximum 640px | `data-ui-layout="reading"` / `"settings"` |
|
|
23
|
+
| Short form | Maximum 480px | `data-ui-layout="short-form"` |
|
|
24
|
+
| List / dashboard / table region | Available space inside the frame, capped at 1600px | `data-ui-layout="list"` |
|
|
25
|
+
| Intentionally unrestricted region | Available parent width | `data-ui-layout="fluid"` |
|
|
26
|
+
|
|
27
|
+
Use `.ui-app-frame` on main. Shared header/footer already apply the frame internally; **do not wrap them in another padded frame**. Put purpose-specific sections inside main. At 1920px the default frame has 1520px available after its 40px gutters. Small screens shrink the frame and reduce gutters to at least 16px. A narrow form must not constrain or move navigation.
|
|
28
|
+
|
|
29
|
+
Reserve a consistent scrollbar gutter in the consumer's global CSS when routes can alternate between short and tall content:
|
|
30
|
+
|
|
31
|
+
```css
|
|
32
|
+
html { scrollbar-gutter: stable; }
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This keeps a centered frame from shifting when a classic scrollbar appears. The document scroll policy belongs to the application; the shared components do not set it implicitly. In a 1920px Chromium viewport with a 15px scrollbar, the frame starts at 152.5px with this policy in both short and tall states. Without it, the start changes from 160px to 152.5px. Overlay-scrollbar platforms may reserve no extra space.
|
|
36
|
+
|
|
37
|
+
## Canonical naeil application starter
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import type { ReactNode } from "react";
|
|
41
|
+
import { NaeilHeader } from "@naeil/ui/naeil-header";
|
|
42
|
+
import { SiteFooter } from "@naeil/ui/ui";
|
|
43
|
+
import { Logo } from "@naeil/ui/components/logo";
|
|
44
|
+
|
|
45
|
+
// Render inside the existing next-themes ThemeProvider.
|
|
46
|
+
// Connect Link/onNavigate, locale and authSlot to real application state.
|
|
47
|
+
export function ApplicationLayout({ children }: { children: ReactNode }) {
|
|
48
|
+
return <>
|
|
49
|
+
<NaeilHeader locale="en" />
|
|
50
|
+
<main className="ui-app-frame"
|
|
51
|
+
style={{ marginTop: "calc(var(--ui-navigation-height) + 1px)" }}>
|
|
52
|
+
{children}
|
|
53
|
+
</main>
|
|
54
|
+
<SiteFooter variant="compact"
|
|
55
|
+
brand={<Logo variant="footer" size={20} />} />
|
|
56
|
+
</>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
<ApplicationLayout>
|
|
60
|
+
<section data-ui-layout="settings">{/* Settings form */}</section>
|
|
61
|
+
</ApplicationLayout>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`NaeilHeader` is fixed; reserve its 65px height and account for it in sticky areas and focus/anchor scroll margins. Default destinations are official naeil.dev URLs. Preserve routing/unsaved-change guards using the [header contract](naeil-header.md). Only pass `onLocaleChange` when the application supports its complete language set.
|
|
65
|
+
|
|
66
|
+
For a different product menu, use the [generic Navigation composition](navigation.md) with the same main/footer structure. Generic Navigation is in document flow by default and needs no fixed-header clearance. The [standalone React example](https://github.com/naeil-dev/naeil-ui/tree/main/examples/react) exercises the installed header, narrow form and SiteFooter together.
|
|
67
|
+
|
|
68
|
+
## Deliberate overrides
|
|
69
|
+
|
|
70
|
+
Apply overrides on a common ancestor or document root so header, main and footer inherit them together. Explicit `containerClassName` overrides still work. Do not give each route its own header cap or gutter.
|
|
71
|
+
|
|
72
|
+
```css
|
|
73
|
+
/* Product exception: record why this cap is needed. */
|
|
74
|
+
.product-shell {
|
|
75
|
+
--ui-width-frame: 1280px;
|
|
76
|
+
--ui-frame-gutter: 24px;
|
|
77
|
+
}
|
|
78
|
+
/* An existing focused list can retain its previous content cap. */
|
|
79
|
+
.focused-list { --ui-width-list: 1200px; }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The new frame/list caps replace the previous 1280px branded header and 1200px list defaults. Remove old `max-w-7xl` or `px-6` frame overrides when adopting the new default. Shared numeric values belong in `src/tokens/ui.json`; regenerate CSS with `pnpm build:tokens`.
|
|
83
|
+
|
|
84
|
+
## Consumer agent instructions
|
|
85
|
+
|
|
86
|
+
Put this in the **consumer application's** `AGENTS.md` (or equivalent repository instructions). Installing a package does not automatically load this repository's agent rules.
|
|
87
|
+
|
|
88
|
+
```markdown
|
|
89
|
+
## Shared application layout
|
|
90
|
+
- Read the installed @naeil/ui/DESIGN.md and docs/components/application-layout.md before UI layout work.
|
|
91
|
+
- Use @naeil/ui/naeil-header for the canonical naeil header; use Navigation from @naeil/ui/ui for a different product menu. Use SiteFooter for application footers. Keep existing Next Nav/Footer compatibility adapters when applicable.
|
|
92
|
+
- Local wrappers supply menu data, routing/guards, locale, auth and slots. Do not copy the menu, mobile sheet, controls or footer implementation. Extend the shared component for reusable missing behavior.
|
|
93
|
+
- Use ui-app-frame on main; navigation/footer own their internal frames. Keep reading/settings/short-form sections inside that frame. Apply frame overrides consistently across routes.
|
|
94
|
+
- Check the installed package version and CSS before creating a replacement for an apparently missing component.
|
|
95
|
+
- Verify actual shared navigation/footer rendering and stable anchors across routes, themes and supported sizes. An import alone is not adoption evidence. Record intentional route exceptions.
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Adoption checks
|
|
99
|
+
|
|
100
|
+
For each consumer, record its installed version, layout adapter, shared imports, CSS entrypoints and intentional exceptions. In that consumer's browser CI, check that expected application routes render `[data-slot="navigation"]` and `[data-slot="site-footer"]`, and that anchors remain stable between wide lists and narrow forms. Include narrow/zoomed screens, localized labels and tall content that introduces a scrollbar. Keep table overflow local.
|
|
101
|
+
|
|
102
|
+
A blanket ban on `<header>`, `<nav>` or `<footer>` would also reject valid section landmarks and adapters. Review the root composition and its rendered output instead. This repository checks packed React/Next examples; external applications still need their own upgrades, adapter migrations and CI. Publication and deployment are separate work.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Shared usage contract
|
|
2
2
|
|
|
3
|
-
These guides
|
|
3
|
+
These guides target the **@naeil/ui 0.6.0**; release identities and publication evidence are recorded in the [release record](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.6.0). The [hosted Docs](https://naeil-dev.github.io/naeil-ui/) were last verified for [0.5.0 on 2026-10-15](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.5.0), while `pnpm storybook` shows this checkout. Start with the [consumer setup and migration](../design/v2-migration.md). Primitives and generic Navigation/SiteFooter are exported from `@naeil/ui/ui`; the actual branded whole header uses the opt-in [NaeilHeader](naeil-header.md) entry `@naeil/ui/naeil-header`. Both are React-only. The legacy root needs the optional Next/next-intl peers. Examples import real wrappers; product state, validation and network requests belong to consumers.
|
|
4
4
|
|
|
5
5
|
## Styling, themes and overrides
|
|
6
6
|
|
|
7
7
|
Import `@naeil/ui/globals.css` once and register `@source` for the installed package's `dist` in your Tailwind 4 build. Alternatively load both theme.css and components.css alongside your existing Tailwind setup. Deliver Pretendard and, for Japanese, Noto Sans JP locally through your app's font pipeline; the package does not fetch fonts. Keep font OFL notices with redistributed binaries. The independent React example demonstrates consumer-owned delivery.
|
|
8
8
|
|
|
9
|
-
Use `.light`/`.dark` on the document root to override OS theme. Root `data-ui-density="compact"` changes spacing, not text size: default controls are 40px, compact 36px, small screens/coarse pointers at least 44px. Portals use document-level styles/density, so a locally themed subtree alone does not theme portaled content.
|
|
9
|
+
Use `.light`/`.dark` on the document root to override OS theme. Root `data-ui-density="compact"` changes spacing, not text size: default controls are 40px, compact 36px, small screens/coarse pointers at least 44px. Portals use document-level styles/density, so a locally themed subtree alone does not theme portaled content. Header, main and footer share a 1600px maximum outer frame and `clamp(16px, 2.4vw, 40px)` gutters. Use `ui-app-frame` on main; shared header/footer components apply that frame internally. Reading/settings sections remain capped at 640px, short forms at 480px, and lists at 1600px within the available frame space. Product root token overrides and explicit component classes remain supported. The [application layout guide](application-layout.md) explains composition, adoption and consistent overrides.
|
|
10
10
|
|
|
11
11
|
`className` is merged by `cn` (clsx + tailwind-merge). Consumer classes and primitive props remain available. Recheck specificity, text/control/focus contrast, mobile targets and portaled content after overriding. Override semantic color companions together; do not replace only an action hue or remove focus outlines. Reduced-motion rules remove shared transitions/animations. Custom consumer animation still needs its own reduced-motion treatment.
|
|
12
12
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# NaeilHeader — branded whole-header composition
|
|
2
|
+
|
|
3
|
+
This guide targets the **@naeil/ui 0.6.0**. See the [release record](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.6.0) for archive and publication evidence. Import this opt-in React-only entry when an application inherits the actual naeil.dev header. Generic `Navigation` from `/ui` remains available for different product menus.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { NaeilHeader } from '@naeil/ui/naeil-header';
|
|
7
|
+
import '@naeil/ui/globals.css';
|
|
8
|
+
|
|
9
|
+
// Inside the consumer's existing next-themes ThemeProvider:
|
|
10
|
+
<NaeilHeader locale="ja" Link={GuardedLink} currentHref={pathname}
|
|
11
|
+
authSlot={<LogoutButton />} />
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Load shared styles once. Apps that already own their Tailwind/base styles may import both `@naeil/ui/theme.css` and `@naeil/ui/components.css` instead of globals. Tailwind 4 consumers must register the installed `dist` with `@source`; see [setup](../design/v2-migration.md#시작하기).
|
|
15
|
+
|
|
16
|
+
The module owns the brand, five-project catalog and header-only translations, project artwork references/status, theme/optional locale controls, toolbar order, fixed positioning, branded frame and Radix desktop/mobile behavior. Do not copy menu JSX or styles into a consumer. The website's `NavWrapper` imports this same module and supplies locale/router/auth state; estimate consumes the built package entry. Legacy `Nav` retains its Next/next-intl compatibility API.
|
|
17
|
+
|
|
18
|
+
## Consumer contract
|
|
19
|
+
|
|
20
|
+
Consumers provide an existing next-themes provider, routing/dirty-save guard, current locale and real authentication behavior. Default destinations point to `https://naeil.dev/{locale}`; the site's localized Link instead uses `resolveHref={path => path}`. A supplied `Link` handles default official destinations, including cross-origin URLs. `onNavigate` can intercept brand and menu links; honor modified clicks and target behavior. Explicit `external: true` project overrides and `projectsExternal` use native anchors, so intercept them with `onNavigate` when a dirty guard is required.
|
|
21
|
+
|
|
22
|
+
Only provide `onLocaleChange` when the application's interface supports all three canonical locales (`en`, `ko`, `ja`). Omitting it removes the locale control; the component does not create application translations or shared preferences. `authSlot` contains the consumer's real session action and must retain at least 44px touch targets. It does not integrate site SSO, share credentials or create accounts. Theme preferences remain consumer-origin state. Without a working provider, rendering alone does not establish theme switching.
|
|
23
|
+
|
|
24
|
+
| Prop | Contract |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `locale` | Required `en`, `ko` or `ja`; selects header-only copy. |
|
|
27
|
+
| `resolveHref` | Resolves canonical unlocalized paths; defaults to the official localized site. |
|
|
28
|
+
| `Link`, `currentHref`, `onNavigate` | Routing adapter, active destination and navigation interception inherited from Navigation. |
|
|
29
|
+
| `onLocaleChange` | Optional supported-language callback; omit for a single-language application. |
|
|
30
|
+
| `authSlot` | Consumer-owned authentication action in the default toolbar. |
|
|
31
|
+
| `brandHref`, `brandSlot`, `brandLinkProps`, `brandLinkComponent` | Explicit brand destination/content/anchor overrides; default accessible brand name is `.naeil`. |
|
|
32
|
+
| `projects`, `projectsExternal`, `showProjects`, `showBlog` | Legacy catalog/visibility/native-link overrides; defaults use the complete canonical catalog. |
|
|
33
|
+
| `subNav`, `currentProjectKey`, `currentProjectHref` | Legacy extra links and current-project destination. |
|
|
34
|
+
| `translations`, `labels` | Legacy header/project lookup overrides and navigation/menu/close accessible labels. |
|
|
35
|
+
| `toolbarSlot` | Replaces the complete default theme/locale/auth toolbar. |
|
|
36
|
+
| `mobileFooter`, `className`, `containerClassName` | Inherited mobile slot and style overrides; recheck frame/behavior after changes. |
|
|
37
|
+
|
|
38
|
+
Standard header attributes and the remaining inherited [Navigation props](navigation.md) remain available. Compatibility overrides preserve existing consumers; new applications normally use the canonical composition.
|
|
39
|
+
|
|
40
|
+
## Frame and content clearance
|
|
41
|
+
|
|
42
|
+
The fixed header is 65px: the current 64px navigation token plus a 1px border. Clear it in the application's main, sticky regions and focus/anchor scroll margins with `calc(var(--ui-navigation-height) + 1px)`. The header, main and footer now share a 1600px maximum outer frame, including responsive `clamp(16px, 2.4vw, 40px)` gutters. Put `ui-app-frame` on main and reading/settings or short-form sections inside it; the header and shared footer apply their own internal frame. Do not wrap them in another padded frame. Apply deliberate `--ui-width-frame` / `--ui-frame-gutter` overrides on a common ancestor. Remove old `max-w-7xl` or `px-6` overrides to adopt the new defaults. This width update supersedes the previous 1280px/24px branded frame. See the [application layout starter](application-layout.md).
|
|
43
|
+
|
|
44
|
+
## Artwork and package boundary
|
|
45
|
+
|
|
46
|
+
The five decorative images reference `https://naeil.dev/images/{coral,jellyfish,whale,fish,turtle}.png`. Rendering those images requires network access to the official origin. Their bytes are not redistributed in npm; [third-party notices](../../THIRD_PARTY_NOTICES.md) retain unresolved artwork provenance and redistribution limits. Brand marks are not relicensed by the repository MIT license. Captions and links remain usable when images fail.
|
|
47
|
+
|
|
48
|
+
This entry requires no Next.js, next-intl, site session/auth backend, full application message catalog, hero or site public folder. Generic `/ui` imports do not import its catalog. The local Storybook provides **Brand / NaeilHeader / Docs, Usage, JapaneseApplication**; the Japanese example omits unsupported locale switching and uses a consumer-owned auth slot. Candidate examples do not establish hosted deployment.
|
|
49
|
+
|
|
50
|
+
Verify actual packed consumption, Storybook behavior and rendered comparisons at matching viewport/language/theme. Source imports alone do not prove visual parity. Native Safari, devices, assistive technology, full OAuth and unresolved accessibility observations retain the [documented support limits](../design/public-ui-support.md).
|