@xex/ui 0.2.1 → 0.3.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/dist/index.d.ts +27 -6
- package/dist/index.js +64 -20
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -309,10 +309,15 @@ interface HeaderProps {
|
|
|
309
309
|
title: string;
|
|
310
310
|
subtitle?: string;
|
|
311
311
|
actions?: ReactNode;
|
|
312
|
-
/** Opens the navigation drawer
|
|
312
|
+
/** Opens the navigation drawer. */
|
|
313
313
|
onMenu?: () => void;
|
|
314
|
+
/**
|
|
315
|
+
* Show the menu button at every width. By default it only shows below lg, where the navigation is a
|
|
316
|
+
* drawer; set this when the user has collapsed the navigation on a wide screen.
|
|
317
|
+
*/
|
|
318
|
+
menuAlwaysVisible?: boolean;
|
|
314
319
|
}
|
|
315
|
-
declare function Header({ title, subtitle, actions, onMenu }: HeaderProps): react.JSX.Element;
|
|
320
|
+
declare function Header({ title, subtitle, actions, onMenu, menuAlwaysVisible }: HeaderProps): react.JSX.Element;
|
|
316
321
|
|
|
317
322
|
/** One entry in a page's "On this page" guide. */
|
|
318
323
|
interface PageSection {
|
|
@@ -345,12 +350,28 @@ interface SidebarProps {
|
|
|
345
350
|
items: NavEntry[];
|
|
346
351
|
active: string;
|
|
347
352
|
onSelect: (id: string) => void;
|
|
348
|
-
/**
|
|
353
|
+
/** Drawer state: whether the slide-over navigation is showing (small screens, or when `collapsed`). */
|
|
349
354
|
open: boolean;
|
|
350
355
|
onClose: () => void;
|
|
356
|
+
/**
|
|
357
|
+
* The user has collapsed the navigation to give the page more room. On wide screens it then behaves
|
|
358
|
+
* as it does on narrow ones: hidden, and opened as a slide-over drawer from the header's menu button.
|
|
359
|
+
*/
|
|
360
|
+
collapsed?: boolean;
|
|
361
|
+
/**
|
|
362
|
+
* Collapse (when docked) or pin open again (when collapsed). Omit to hide the button and keep the
|
|
363
|
+
* sidebar always docked on wide screens.
|
|
364
|
+
*/
|
|
365
|
+
onToggleCollapse?: () => void;
|
|
351
366
|
}
|
|
352
|
-
/** App navigation:
|
|
353
|
-
declare function Sidebar({ items, active, onSelect, open, onClose }: SidebarProps): react.JSX.Element;
|
|
367
|
+
/** App navigation: docked at lg+ (collapsible), slide-over drawer below, or when collapsed. */
|
|
368
|
+
declare function Sidebar({ items, active, onSelect, open, onClose, collapsed, onToggleCollapse }: SidebarProps): react.JSX.Element;
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Whether the user has collapsed the sidebar, remembered in localStorage.
|
|
372
|
+
* Give each app its own key (like ThemeProvider's `storagePrefix`), e.g. `my-app-nav-collapsed`.
|
|
373
|
+
*/
|
|
374
|
+
declare function useSidebarCollapsed(storageKey?: string): [collapsed: boolean, setCollapsed: (value: boolean) => void];
|
|
354
375
|
|
|
355
376
|
type ColorMode = 'light' | 'dark';
|
|
356
377
|
interface ThemeContextValue {
|
|
@@ -387,4 +408,4 @@ declare function ThemeSwitcher({ showLabels }: {
|
|
|
387
408
|
showLabels?: boolean;
|
|
388
409
|
}): react.JSX.Element;
|
|
389
410
|
|
|
390
|
-
export { AVATAR_COLORS, Avatar, type AvatarProps, type AvatarSize, Badge, type BadgeProps, type BadgeVariant, Button, type ButtonProps, type ButtonSize, type ButtonVariant, type ColorMode, ConfirmDialog, type ConfirmDialogProps, DataTable, type DataTableColumn, type DataTableProps, type FetchState, FetchStatus, type FetchStatusProps, Header, Input, type InputProps, LoadingDots, LoadingOverlay, type LoadingOverlayProps, Modal, type ModalProps, type ModalSize, MultiSelect, type MultiSelectOption, type MultiSelectProps, type NavEntry, PageGuide, type PageSection, PaginationBar, type PaginationBarProps, ProgressBar, type ProgressBarProps, ProgressRing, type ProgressRingProps, type ProgressTone, Section, SegmentedControl, type SegmentedControlProps, type SegmentedOption, Select, type SelectOption, type SelectProps, Sidebar, Skeleton, type SkeletonProps, SkeletonText, Spinner, type SpinnerProps, type SpinnerSize, type TabItem, Tabs, type TabsProps, type ThemeContextValue, ThemeProvider, type ThemeProviderProps, ThemeSwitcher, colorFor, focusablesIn, initialsOf, pageWindow, radioGroupKeyDown, useDialogFocus, useTheme };
|
|
411
|
+
export { AVATAR_COLORS, Avatar, type AvatarProps, type AvatarSize, Badge, type BadgeProps, type BadgeVariant, Button, type ButtonProps, type ButtonSize, type ButtonVariant, type ColorMode, ConfirmDialog, type ConfirmDialogProps, DataTable, type DataTableColumn, type DataTableProps, type FetchState, FetchStatus, type FetchStatusProps, Header, Input, type InputProps, LoadingDots, LoadingOverlay, type LoadingOverlayProps, Modal, type ModalProps, type ModalSize, MultiSelect, type MultiSelectOption, type MultiSelectProps, type NavEntry, PageGuide, type PageSection, PaginationBar, type PaginationBarProps, ProgressBar, type ProgressBarProps, ProgressRing, type ProgressRingProps, type ProgressTone, Section, SegmentedControl, type SegmentedControlProps, type SegmentedOption, Select, type SelectOption, type SelectProps, Sidebar, Skeleton, type SkeletonProps, SkeletonText, Spinner, type SpinnerProps, type SpinnerSize, type TabItem, Tabs, type TabsProps, type ThemeContextValue, ThemeProvider, type ThemeProviderProps, ThemeSwitcher, colorFor, focusablesIn, initialsOf, pageWindow, radioGroupKeyDown, useDialogFocus, useSidebarCollapsed, useTheme };
|
package/dist/index.js
CHANGED
|
@@ -1104,9 +1104,10 @@ function Tabs({
|
|
|
1104
1104
|
}
|
|
1105
1105
|
|
|
1106
1106
|
// src/layout/Header.tsx
|
|
1107
|
+
import clsx13 from "clsx";
|
|
1107
1108
|
import { Bars3Icon } from "@heroicons/react/24/outline";
|
|
1108
1109
|
import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1109
|
-
function Header({ title, subtitle, actions, onMenu }) {
|
|
1110
|
+
function Header({ title, subtitle, actions, onMenu, menuAlwaysVisible = false }) {
|
|
1110
1111
|
return /* @__PURE__ */ jsxs13("header", { className: "sticky top-0 z-30 flex flex-wrap items-center justify-between gap-3 border-b border-gray-200 bg-surface px-4 py-4 sm:px-6", children: [
|
|
1111
1112
|
/* @__PURE__ */ jsxs13("div", { className: "flex min-w-0 items-center gap-3", children: [
|
|
1112
1113
|
onMenu && /* @__PURE__ */ jsx16(
|
|
@@ -1115,7 +1116,10 @@ function Header({ title, subtitle, actions, onMenu }) {
|
|
|
1115
1116
|
type: "button",
|
|
1116
1117
|
onClick: onMenu,
|
|
1117
1118
|
"aria-label": "Open navigation",
|
|
1118
|
-
className:
|
|
1119
|
+
className: clsx13(
|
|
1120
|
+
"rounded-lg p-1.5 text-gray-600 hover:bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400",
|
|
1121
|
+
!menuAlwaysVisible && "lg:hidden"
|
|
1122
|
+
),
|
|
1119
1123
|
children: /* @__PURE__ */ jsx16(Bars3Icon, { className: "h-6 w-6" })
|
|
1120
1124
|
}
|
|
1121
1125
|
),
|
|
@@ -1130,7 +1134,7 @@ function Header({ title, subtitle, actions, onMenu }) {
|
|
|
1130
1134
|
|
|
1131
1135
|
// src/layout/PageGuide.tsx
|
|
1132
1136
|
import { useCallback as useCallback2, useLayoutEffect as useLayoutEffect2, useRef as useRef5, useState as useState4 } from "react";
|
|
1133
|
-
import
|
|
1137
|
+
import clsx14 from "clsx";
|
|
1134
1138
|
import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1135
1139
|
var OFFSET = 96;
|
|
1136
1140
|
var reducedMotion = () => window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
@@ -1250,7 +1254,7 @@ function PageGuide({ sections }) {
|
|
|
1250
1254
|
onPointerMove,
|
|
1251
1255
|
onPointerUp: endDrag,
|
|
1252
1256
|
onPointerCancel: endDrag,
|
|
1253
|
-
className:
|
|
1257
|
+
className: clsx14("absolute -left-2 bottom-0 top-0 z-10 w-5 touch-none select-none", dragging ? "cursor-grabbing" : "cursor-grab")
|
|
1254
1258
|
}
|
|
1255
1259
|
),
|
|
1256
1260
|
/* @__PURE__ */ jsx17("span", { "aria-hidden": "true", className: "absolute bottom-0 left-0 top-0 w-px bg-gray-200" }),
|
|
@@ -1259,7 +1263,7 @@ function PageGuide({ sections }) {
|
|
|
1259
1263
|
{
|
|
1260
1264
|
ref: markerRef,
|
|
1261
1265
|
"aria-hidden": "true",
|
|
1262
|
-
className:
|
|
1266
|
+
className: clsx14(
|
|
1263
1267
|
"pointer-events-none absolute left-0 top-0 rounded-full bg-brand-600 transition-[width,margin] duration-150",
|
|
1264
1268
|
dragging ? "-ml-px w-1" : "w-0.5 group-hover:-ml-px group-hover:w-1"
|
|
1265
1269
|
)
|
|
@@ -1277,7 +1281,7 @@ function PageGuide({ sections }) {
|
|
|
1277
1281
|
e.preventDefault();
|
|
1278
1282
|
goTo(i, e.detail === 0);
|
|
1279
1283
|
},
|
|
1280
|
-
className:
|
|
1284
|
+
className: clsx14(
|
|
1281
1285
|
"block py-1.5 pl-3 pr-2 text-sm transition-colors",
|
|
1282
1286
|
i === active ? "font-medium text-accent" : "text-gray-500 hover:text-gray-900"
|
|
1283
1287
|
),
|
|
@@ -1300,41 +1304,44 @@ function Section({ id, title, description, children }) {
|
|
|
1300
1304
|
|
|
1301
1305
|
// src/layout/Sidebar.tsx
|
|
1302
1306
|
import { useEffect as useEffect5, useRef as useRef6 } from "react";
|
|
1303
|
-
import
|
|
1304
|
-
import { XMarkIcon as XMarkIcon3 } from "@heroicons/react/24/outline";
|
|
1307
|
+
import clsx15 from "clsx";
|
|
1308
|
+
import { ChevronDoubleLeftIcon, ChevronDoubleRightIcon, XMarkIcon as XMarkIcon3 } from "@heroicons/react/24/outline";
|
|
1305
1309
|
import { Fragment as Fragment2, jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1306
|
-
|
|
1310
|
+
var iconButton = "rounded-lg p-1 text-gray-500 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400";
|
|
1311
|
+
function Sidebar({ items, active, onSelect, open, onClose, collapsed = false, onToggleCollapse }) {
|
|
1307
1312
|
const asideRef = useRef6(null);
|
|
1308
1313
|
const trapTab = useDialogFocus(open, asideRef, {
|
|
1309
1314
|
initialFocus: () => asideRef.current?.querySelector('[aria-current="page"]')
|
|
1310
1315
|
});
|
|
1311
1316
|
useEffect5(() => {
|
|
1312
|
-
if (!open) return;
|
|
1317
|
+
if (!open || collapsed) return;
|
|
1313
1318
|
const mq = window.matchMedia("(min-width: 1024px)");
|
|
1314
1319
|
const onChange = () => mq.matches && onClose();
|
|
1315
1320
|
mq.addEventListener("change", onChange);
|
|
1316
1321
|
return () => mq.removeEventListener("change", onChange);
|
|
1317
|
-
}, [open, onClose]);
|
|
1322
|
+
}, [open, collapsed, onClose]);
|
|
1318
1323
|
useEffect5(() => {
|
|
1319
1324
|
if (!open) return;
|
|
1320
1325
|
const onKey = (e) => e.key === "Escape" && onClose();
|
|
1321
1326
|
document.addEventListener("keydown", onKey);
|
|
1322
1327
|
return () => document.removeEventListener("keydown", onKey);
|
|
1323
1328
|
}, [open, onClose]);
|
|
1329
|
+
const docked = !collapsed;
|
|
1324
1330
|
return /* @__PURE__ */ jsxs16(Fragment2, { children: [
|
|
1325
|
-
open && /* @__PURE__ */ jsx19("div", { className: "fixed inset-0 z-40 bg-black/40 backdrop-blur-sm lg:hidden", onClick: onClose, "aria-hidden": "true" }),
|
|
1331
|
+
open && /* @__PURE__ */ jsx19("div", { className: clsx15("fixed inset-0 z-40 bg-black/40 backdrop-blur-sm", docked && "lg:hidden"), onClick: onClose, "aria-hidden": "true" }),
|
|
1326
1332
|
/* @__PURE__ */ jsxs16(
|
|
1327
1333
|
"aside",
|
|
1328
1334
|
{
|
|
1329
1335
|
ref: asideRef,
|
|
1330
1336
|
"aria-label": "Main navigation",
|
|
1337
|
+
"data-collapsed": collapsed || void 0,
|
|
1331
1338
|
onKeyDown: open ? trapTab : void 0,
|
|
1332
|
-
className:
|
|
1339
|
+
className: clsx15(
|
|
1333
1340
|
"fixed inset-y-0 left-0 z-50 flex w-64 flex-col border-r border-gray-200 bg-surface duration-200",
|
|
1334
|
-
"lg:sticky lg:top-0 lg:z-auto lg:h-screen lg:w-60 lg:shrink-0 lg:translate-x-0",
|
|
1335
|
-
//
|
|
1336
|
-
//
|
|
1337
|
-
open ? "visible translate-x-0 shadow-2xl transition-transform" : "invisible -translate-x-full transition-[transform,visibility] lg:visible"
|
|
1341
|
+
docked && "lg:sticky lg:top-0 lg:z-auto lg:h-screen lg:w-60 lg:shrink-0 lg:translate-x-0",
|
|
1342
|
+
// Hidden drawer: invisible = out of the tab order. It switches off only after the slide-out
|
|
1343
|
+
// finishes, but switches on immediately so focus can move in on open.
|
|
1344
|
+
open ? "visible translate-x-0 shadow-2xl transition-transform" : clsx15("invisible -translate-x-full transition-[transform,visibility]", docked && "lg:visible")
|
|
1338
1345
|
),
|
|
1339
1346
|
children: [
|
|
1340
1347
|
/* @__PURE__ */ jsxs16("div", { className: "flex items-center justify-between px-5 py-5", children: [
|
|
@@ -1342,7 +1349,20 @@ function Sidebar({ items, active, onSelect, open, onClose }) {
|
|
|
1342
1349
|
/* @__PURE__ */ jsx19("span", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-brand-600 text-sm font-bold text-white", children: "X" }),
|
|
1343
1350
|
/* @__PURE__ */ jsx19("span", { className: "text-lg font-bold tracking-tight text-gray-900", children: "XeX" })
|
|
1344
1351
|
] }),
|
|
1345
|
-
/* @__PURE__ */
|
|
1352
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-1", children: [
|
|
1353
|
+
onToggleCollapse && /* @__PURE__ */ jsx19(
|
|
1354
|
+
"button",
|
|
1355
|
+
{
|
|
1356
|
+
type: "button",
|
|
1357
|
+
onClick: onToggleCollapse,
|
|
1358
|
+
"aria-label": collapsed ? "Keep navigation open" : "Collapse navigation",
|
|
1359
|
+
title: collapsed ? "Keep navigation open" : "Collapse navigation",
|
|
1360
|
+
className: clsx15(iconButton, "hidden lg:inline-flex"),
|
|
1361
|
+
children: collapsed ? /* @__PURE__ */ jsx19(ChevronDoubleRightIcon, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx19(ChevronDoubleLeftIcon, { className: "h-5 w-5" })
|
|
1362
|
+
}
|
|
1363
|
+
),
|
|
1364
|
+
/* @__PURE__ */ jsx19("button", { type: "button", onClick: onClose, "aria-label": "Close navigation", className: clsx15(iconButton, docked && "lg:hidden"), children: /* @__PURE__ */ jsx19(XMarkIcon3, { className: "h-5 w-5" }) })
|
|
1365
|
+
] })
|
|
1346
1366
|
] }),
|
|
1347
1367
|
/* @__PURE__ */ jsx19("nav", { className: "flex-1 space-y-1 overflow-y-auto px-3 pb-4 pt-2", children: items.map(({ id, label, icon: Icon }) => /* @__PURE__ */ jsxs16(
|
|
1348
1368
|
"a",
|
|
@@ -1367,8 +1387,31 @@ function Sidebar({ items, active, onSelect, open, onClose }) {
|
|
|
1367
1387
|
] });
|
|
1368
1388
|
}
|
|
1369
1389
|
|
|
1390
|
+
// src/layout/useSidebarCollapsed.ts
|
|
1391
|
+
import { useCallback as useCallback3, useState as useState5 } from "react";
|
|
1392
|
+
function useSidebarCollapsed(storageKey = "xex-nav-collapsed") {
|
|
1393
|
+
const [collapsed, setState] = useState5(() => {
|
|
1394
|
+
try {
|
|
1395
|
+
return localStorage.getItem(storageKey) === "1";
|
|
1396
|
+
} catch {
|
|
1397
|
+
return false;
|
|
1398
|
+
}
|
|
1399
|
+
});
|
|
1400
|
+
const setCollapsed = useCallback3(
|
|
1401
|
+
(value) => {
|
|
1402
|
+
setState(value);
|
|
1403
|
+
try {
|
|
1404
|
+
localStorage.setItem(storageKey, value ? "1" : "0");
|
|
1405
|
+
} catch {
|
|
1406
|
+
}
|
|
1407
|
+
},
|
|
1408
|
+
[storageKey]
|
|
1409
|
+
);
|
|
1410
|
+
return [collapsed, setCollapsed];
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1370
1413
|
// src/theme/ThemeSwitcher.tsx
|
|
1371
|
-
import
|
|
1414
|
+
import clsx16 from "clsx";
|
|
1372
1415
|
import { CheckIcon, MoonIcon, SunIcon } from "@heroicons/react/24/solid";
|
|
1373
1416
|
import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1374
1417
|
function ThemeSwitcher({ showLabels = false }) {
|
|
@@ -1385,7 +1428,7 @@ function ThemeSwitcher({ showLabels = false }) {
|
|
|
1385
1428
|
tabIndex: selected ? 0 : -1,
|
|
1386
1429
|
title: t.name,
|
|
1387
1430
|
onClick: () => setThemeId(t.id),
|
|
1388
|
-
className:
|
|
1431
|
+
className: clsx16(
|
|
1389
1432
|
"flex items-center gap-2 rounded-full border p-1 transition focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400",
|
|
1390
1433
|
showLabels && "pr-3",
|
|
1391
1434
|
selected ? "border-gray-400" : "border-transparent hover:border-gray-300"
|
|
@@ -1454,5 +1497,6 @@ export {
|
|
|
1454
1497
|
pageWindow,
|
|
1455
1498
|
radioGroupKeyDown,
|
|
1456
1499
|
useDialogFocus,
|
|
1500
|
+
useSidebarCollapsed,
|
|
1457
1501
|
useTheme
|
|
1458
1502
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xex/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "XeX design system: React components, app shell and theme provider.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@heroicons/react": "^2.2.0",
|
|
55
|
-
"@xex/tokens": "^0.
|
|
55
|
+
"@xex/tokens": "^0.3.0",
|
|
56
56
|
"clsx": "^2.1.1"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|