@campfire-interactive/side-nav 0.1.2 → 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/dist/index.d.ts +42 -4
- package/dist/index.js +21 -8
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
interface NavItem {
|
|
@@ -12,13 +12,51 @@ interface NavItem {
|
|
|
12
12
|
path?: string;
|
|
13
13
|
/** Optional badge (count, "new", etc). */
|
|
14
14
|
badge?: string | number;
|
|
15
|
+
/**
|
|
16
|
+
* Renders the row unavailable rather than hiding it.
|
|
17
|
+
*
|
|
18
|
+
* For a page that exists but needs something chosen first — pm's program tabs
|
|
19
|
+
* with no program picked. Hiding them instead makes the nav change shape as
|
|
20
|
+
* you move through the app, which leaves a new reader with no idea what it
|
|
21
|
+
* contains; and linking them anyway lands on a route that resolves against
|
|
22
|
+
* `/` or 404s.
|
|
23
|
+
*
|
|
24
|
+
* Distinct from simply leaving the item out, which is right for a page the
|
|
25
|
+
* build does not have at all.
|
|
26
|
+
*/
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Why it is unavailable, as the row's tooltip and accessible description.
|
|
30
|
+
* A disabled control with no stated reason reads as a bug.
|
|
31
|
+
*/
|
|
32
|
+
disabledReason?: string;
|
|
15
33
|
}
|
|
16
34
|
interface NavGroup {
|
|
17
35
|
id: string;
|
|
18
|
-
/**
|
|
19
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Section header text shown above the items. Optional: a group may exist
|
|
38
|
+
* purely to carry a `header` control and its items, with no caption — which
|
|
39
|
+
* is what pm needs, and what omsf does (it separates groups with a rule,
|
|
40
|
+
* not a label).
|
|
41
|
+
*/
|
|
42
|
+
label?: string;
|
|
20
43
|
/** Items in this group. */
|
|
21
44
|
items: NavItem[];
|
|
45
|
+
/**
|
|
46
|
+
* Rendered between the group label and its items — for a control that scopes
|
|
47
|
+
* the group, such as a picker choosing WHICH program the items below belong
|
|
48
|
+
* to.
|
|
49
|
+
*
|
|
50
|
+
* Hidden when the nav is collapsed, like the label, because an icon rail has
|
|
51
|
+
* no room for a control and a half-visible dropdown is worse than none. The
|
|
52
|
+
* group's items stay reachable; only the scoping control goes.
|
|
53
|
+
*
|
|
54
|
+
* pm is what needed it: its nav is Portfolio / Work, then a program picker
|
|
55
|
+
* above that program's tabs, then a process picker above that process's
|
|
56
|
+
* tabs. That shape has no home in a flat item list, and the bottom `footer`
|
|
57
|
+
* slot is the wrong place for a control belonging to one group.
|
|
58
|
+
*/
|
|
59
|
+
header?: ReactNode;
|
|
22
60
|
}
|
|
23
61
|
/** Discriminated by presence of `items` — groups have it, items don't. */
|
|
24
62
|
type NavEntry = NavItem | NavGroup;
|
|
@@ -53,6 +91,6 @@ interface SideNavProps {
|
|
|
53
91
|
className?: string;
|
|
54
92
|
}
|
|
55
93
|
|
|
56
|
-
declare function SideNav({ items, footerItems, activeRoute, onNavigate, collapsible, collapsed: collapsedProp, onCollapseToggle, isOpen, onClose, footer, className, }: SideNavProps):
|
|
94
|
+
declare function SideNav({ items, footerItems, activeRoute, onNavigate, collapsible, collapsed: collapsedProp, onCollapseToggle, isOpen, onClose, footer, className, }: SideNavProps): react.JSX.Element;
|
|
57
95
|
|
|
58
96
|
export { type NavEntry, type NavGroup, type NavItem, SideNav, type SideNavProps, isNavGroup };
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ function styleInject(css, { insertAt } = {}) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// src/styles.css
|
|
27
|
-
styleInject(".cfi-sn {\n --cfi-sidenav-width
|
|
27
|
+
styleInject(".cfi-sn {\n --cfi-sidenav-width-collapsed: 64px;\n --cfi-sidenav-mobile-breakpoint: 1024px;\n --cfi-sidenav-group-label-fg: var(--cfi-sidenav-fg-muted, #94a3b8);\n --cfi-sidenav-toggle-bg: var(--cfi-sidenav-bg, #1e293b);\n --cfi-sidenav-toggle-border: var(--cfi-sidenav-border, #334155);\n --cfi-sidenav-toggle-fg: var(--cfi-sidenav-fg-muted, #94a3b8);\n --cfi-sidenav-scrollbar-thumb: var(--cfi-sidenav-border, #334155);\n --cfi-sidenav-scrollbar-track: transparent;\n position: relative;\n display: flex;\n flex-direction: column;\n flex-shrink: 0;\n width: var(--cfi-sidenav-width, 256px);\n height: 100%;\n background: var(--cfi-sidenav-bg, #1e293b);\n color: var(--cfi-sidenav-fg, #cbd5e1);\n border-right: 1px solid var(--cfi-sidenav-border, #334155);\n font-family: var(--cfi-font-family, system-ui, sans-serif);\n font-size: var(--cfi-font-size-sm, 0.875rem);\n transition: width 200ms ease;\n}\n.cfi-sn--collapsed {\n width: var(--cfi-sidenav-width-collapsed);\n}\n.cfi-sn-list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n.cfi-sn-list--primary {\n flex: 1;\n padding: var(--cfi-spacing-sm, 0.5rem);\n overflow-y: auto;\n overflow-x: hidden;\n scrollbar-width: thin;\n scrollbar-color: var(--cfi-sidenav-scrollbar-thumb) var(--cfi-sidenav-scrollbar-track);\n}\n.cfi-sn-list--primary::-webkit-scrollbar {\n width: 6px;\n}\n.cfi-sn-list--primary::-webkit-scrollbar-track {\n background: var(--cfi-sidenav-scrollbar-track);\n}\n.cfi-sn-list--primary::-webkit-scrollbar-thumb {\n background: var(--cfi-sidenav-scrollbar-thumb);\n border-radius: 3px;\n}\n.cfi-sn-list--footer {\n padding: var(--cfi-spacing-sm, 0.5rem);\n border-top: 1px solid var(--cfi-sidenav-border, #334155);\n}\n.cfi-sn-item {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-sm, 0.75rem);\n padding: 8px 12px;\n border-radius: 10px;\n color: var(--cfi-sidenav-fg, #cbd5e1);\n font-size: inherit;\n font-weight: 500;\n text-decoration: none;\n cursor: pointer;\n transition: background-color 100ms, color 100ms;\n white-space: nowrap;\n}\n.cfi-sn-item--disabled {\n color: var(--cfi-sidenav-fg-muted, #94a3b8);\n cursor: default;\n}\n.cfi-sn-item--disabled .cfi-sn-icon {\n opacity: 0.55;\n}\n.cfi-sn-item:hover {\n background-color: var(--cfi-sidenav-item-hover-bg, #334155);\n color: var(--cfi-sidenav-item-hover-fg, #ffffff);\n}\n.cfi-sn-item--active,\n.cfi-sn-item--active:hover {\n background-color: var(--cfi-sidenav-item-active-bg, #2563eb);\n color: var(--cfi-sidenav-item-active-fg, #ffffff);\n}\n.cfi-sn-icon {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n}\n.cfi-sn-label {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.cfi-sn-badge {\n flex-shrink: 0;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n border-radius: 9px;\n background: var(--cfi-color-error-strong, #dc2626);\n color: white;\n font-size: 11px;\n font-weight: 500;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.cfi-sn--collapsed .cfi-sn-item {\n justify-content: center;\n padding: 10px;\n}\n.cfi-sn-group {\n list-style: none;\n margin-top: var(--cfi-spacing-sm, 0.5rem);\n padding-top: var(--cfi-spacing-sm, 0.5rem);\n border-top: 1px solid var(--cfi-sidenav-border, #334155);\n}\n.cfi-sn-group:first-child {\n margin-top: 0;\n}\n.cfi-sn-group-label {\n padding: var(--cfi-spacing-xs, 0.25rem) 12px;\n color: var(--cfi-sidenav-group-label-fg);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n.cfi-sn {\n --cfi-ui-surface: transparent;\n --cfi-ui-surface-raised: var(--cfi-sidenav-item-hover-bg, #334155);\n --cfi-ui-surface-hover: var(--cfi-sidenav-item-hover-bg, #334155);\n --cfi-ui-content: var(--cfi-sidenav-fg, #cbd5e1);\n --cfi-ui-content-muted: var(--cfi-sidenav-fg-muted, #94a3b8);\n --cfi-ui-border: var(--cfi-sidenav-border, #334155);\n --cfi-ui-border-strong: var(--cfi-sidenav-border, #334155);\n}\n.cfi-sn-group-header {\n padding: var(--cfi-spacing-xs, 0.25rem) 12px var(--cfi-spacing-sm, 0.5rem);\n}\n.cfi-sn-group-header > * {\n width: 100%;\n}\n.cfi-sn-toggle {\n position: absolute;\n top: var(--cfi-spacing-md, 1rem);\n right: -12px;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n padding: 0;\n background: var(--cfi-sidenav-toggle-bg);\n border: 1px solid var(--cfi-sidenav-toggle-border);\n border-radius: 50%;\n color: var(--cfi-sidenav-toggle-fg);\n cursor: pointer;\n box-shadow: var(--cfi-shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.05));\n transition: color 100ms;\n}\n.cfi-sn-toggle:hover {\n color: var(--cfi-sidenav-fg, #cbd5e1);\n}\n.cfi-sn-footer {\n padding: var(--cfi-spacing-sm, 0.5rem);\n border-top: 1px solid var(--cfi-sidenav-border, #334155);\n color: var(--cfi-sidenav-fg-muted, #94a3b8);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n}\n.cfi-sn-backdrop {\n display: none;\n}\n@media (max-width: 1023px) {\n .cfi-sn--drawer {\n position: fixed;\n top: 0;\n left: 0;\n bottom: 0;\n z-index: 90;\n transform: translateX(0);\n transition: transform 200ms ease;\n }\n .cfi-sn--drawer.cfi-sn--drawer-closed {\n transform: translateX(-100%);\n }\n .cfi-sn-backdrop {\n display: block;\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: 80;\n }\n}\n");
|
|
28
28
|
|
|
29
29
|
// src/SideNav.tsx
|
|
30
30
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
@@ -48,7 +48,23 @@ function NavItemRow({ item, active, collapsed, onNavigate, onClose }) {
|
|
|
48
48
|
onNavigate?.(item, e);
|
|
49
49
|
onClose?.();
|
|
50
50
|
};
|
|
51
|
-
|
|
51
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
52
|
+
item.icon && /* @__PURE__ */ jsx("span", { className: "cfi-sn-icon", children: item.icon }),
|
|
53
|
+
!collapsed && /* @__PURE__ */ jsx("span", { className: "cfi-sn-label", children: item.label }),
|
|
54
|
+
!collapsed && item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: "cfi-sn-badge", children: item.badge })
|
|
55
|
+
] });
|
|
56
|
+
if (item.disabled) {
|
|
57
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
58
|
+
"span",
|
|
59
|
+
{
|
|
60
|
+
className: "cfi-sn-item cfi-sn-item--disabled",
|
|
61
|
+
title: item.disabledReason ?? (collapsed ? item.label : void 0),
|
|
62
|
+
"aria-disabled": "true",
|
|
63
|
+
children: body
|
|
64
|
+
}
|
|
65
|
+
) });
|
|
66
|
+
}
|
|
67
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
52
68
|
"a",
|
|
53
69
|
{
|
|
54
70
|
href,
|
|
@@ -56,11 +72,7 @@ function NavItemRow({ item, active, collapsed, onNavigate, onClose }) {
|
|
|
56
72
|
onClick: handleClick,
|
|
57
73
|
title: collapsed ? item.label : void 0,
|
|
58
74
|
"aria-current": active ? "page" : void 0,
|
|
59
|
-
children:
|
|
60
|
-
item.icon && /* @__PURE__ */ jsx("span", { className: "cfi-sn-icon", children: item.icon }),
|
|
61
|
-
!collapsed && /* @__PURE__ */ jsx("span", { className: "cfi-sn-label", children: item.label }),
|
|
62
|
-
!collapsed && item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: "cfi-sn-badge", children: item.badge })
|
|
63
|
-
]
|
|
75
|
+
children: body
|
|
64
76
|
}
|
|
65
77
|
) });
|
|
66
78
|
}
|
|
@@ -69,7 +81,8 @@ function NavEntries({ entries, activeRoute, collapsed, onNavigate, onClose }) {
|
|
|
69
81
|
if (isNavGroup(entry)) {
|
|
70
82
|
const group = entry;
|
|
71
83
|
return /* @__PURE__ */ jsxs("li", { className: "cfi-sn-group", children: [
|
|
72
|
-
!collapsed && /* @__PURE__ */ jsx("div", { className: "cfi-sn-group-label", children: group.label }),
|
|
84
|
+
!collapsed && group.label && /* @__PURE__ */ jsx("div", { className: "cfi-sn-group-label", children: group.label }),
|
|
85
|
+
!collapsed && group.header && /* @__PURE__ */ jsx("div", { className: "cfi-sn-group-header", children: group.header }),
|
|
73
86
|
/* @__PURE__ */ jsx("ul", { className: "cfi-sn-list", children: group.items.map((item) => /* @__PURE__ */ jsx(
|
|
74
87
|
NavItemRow,
|
|
75
88
|
{
|