@kcascend/liquid-ui 0.0.4 → 0.0.6

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.
@@ -1,8 +1,8 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useId, useMemo, useState } from 'react';
3
- import { Icon } from '../Icon/Icon';
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useId, useMemo, useState } from "react";
3
+ import { Icon } from "../Icon/Icon";
4
4
  function getClassName(className) {
5
- return ['liquid-header-shell', className].filter(Boolean).join(' ');
5
+ return ["liquid-header-shell", className].filter(Boolean).join(" ");
6
6
  }
7
7
  function getFirstEnabledNavigationItem(items) {
8
8
  return items?.find((item) => !item.disabled)?.id;
@@ -10,61 +10,90 @@ function getFirstEnabledNavigationItem(items) {
10
10
  function HeaderNavigationLink({ activeItemId, item, onSelect, }) {
11
11
  const isSelected = item.id === activeItemId;
12
12
  if (item.href) {
13
- return (_jsx("a", { className: "liquid-header-shell__nav-link", "data-selected": isSelected ? 'true' : undefined, href: item.href, onClick: (event) => {
13
+ return (_jsx("a", { className: "liquid-header-shell__nav-link", "data-selected": isSelected ? "true" : undefined, href: item.href, onClick: (event) => {
14
14
  if (item.disabled) {
15
15
  event.preventDefault();
16
16
  return;
17
17
  }
18
18
  onSelect(item);
19
- }, "aria-current": isSelected ? 'page' : undefined, "aria-disabled": item.disabled ? true : undefined, children: item.label }));
19
+ }, "aria-current": isSelected ? "page" : undefined, "aria-disabled": item.disabled ? true : undefined, children: item.label }));
20
20
  }
21
- return (_jsx("button", { className: "liquid-header-shell__nav-link", "data-selected": isSelected ? 'true' : undefined, disabled: item.disabled, onClick: () => {
21
+ return (_jsx("button", { className: "liquid-header-shell__nav-link", "data-selected": isSelected ? "true" : undefined, disabled: item.disabled, onClick: () => {
22
22
  if (!item.disabled) {
23
23
  onSelect(item);
24
24
  }
25
- }, type: "button", "aria-current": isSelected ? 'page' : undefined, children: item.label }));
25
+ }, type: "button", "aria-current": isSelected ? "page" : undefined, children: item.label }));
26
26
  }
27
- function HeaderUtilityAction({ action, onAction, }) {
27
+ function HeaderUtilityAction({ action, showLabel = false, onAction, }) {
28
28
  const icon = _jsx(Icon, { name: action.icon, size: action.iconSize ?? 20 });
29
29
  if (action.href) {
30
- return (_jsx("a", { "aria-label": action.label, className: "liquid-header-shell__action", href: action.href, onClick: (event) => {
30
+ return (_jsxs("a", { "aria-label": action.label, className: "liquid-header-shell__action", href: action.href, onClick: (event) => {
31
31
  if (action.disabled) {
32
32
  event.preventDefault();
33
33
  return;
34
34
  }
35
35
  onAction(action);
36
- }, "aria-disabled": action.disabled ? true : undefined, children: icon }));
36
+ }, "aria-disabled": action.disabled ? true : undefined, children: [icon, showLabel ? (_jsx("span", { className: "liquid-header-shell__action-label", children: action.label })) : null] }));
37
37
  }
38
- return (_jsx("button", { "aria-label": action.label, className: "liquid-header-shell__action", disabled: action.disabled, onClick: () => {
38
+ return (_jsxs("button", { "aria-label": action.label, className: "liquid-header-shell__action", disabled: action.disabled, onClick: () => {
39
39
  if (!action.disabled) {
40
40
  onAction(action);
41
41
  }
42
- }, type: "button", children: icon }));
42
+ }, type: "button", children: [icon, showLabel ? (_jsx("span", { className: "liquid-header-shell__action-label", children: action.label })) : null] }));
43
43
  }
44
- export function HeaderShell({ activeNavigationItemId, actions, brand, className, defaultActiveNavigationItemId, menuTriggerLabel = 'Open menu', navigationItems, navLabel = 'Primary', onAction, onMenuTriggerClick, onNavigationChange, showMenuTrigger, style, ...props }) {
44
+ export function HeaderShell({ activeNavigationItemId, actions, brand, className, defaultActiveNavigationItemId, menuTriggerLabel = "Open menu", navigationItems, navLabel = "Primary", onAction, onMenuTriggerClick, onNavigationChange, showMenuTrigger, style, ...props }) {
45
45
  const fallbackActiveId = useMemo(() => getFirstEnabledNavigationItem(navigationItems), [navigationItems]);
46
46
  const [internalActiveId, setInternalActiveId] = useState(defaultActiveNavigationItemId ?? fallbackActiveId);
47
47
  const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
48
48
  const mobileAccordionId = useId();
49
49
  const shouldShowMenuTrigger = showMenuTrigger ?? true;
50
50
  const resolvedActiveId = activeNavigationItemId ?? internalActiveId ?? fallbackActiveId;
51
+ const closeMobileMenu = () => {
52
+ setIsMobileMenuOpen(false);
53
+ };
51
54
  const handleNavigationSelect = (item) => {
52
55
  if (activeNavigationItemId === undefined) {
53
56
  setInternalActiveId(item.id);
54
57
  }
55
58
  onNavigationChange?.(item.id, item);
56
- setIsMobileMenuOpen(false);
59
+ closeMobileMenu();
57
60
  };
58
61
  const handleAction = (action) => {
59
62
  onAction?.(action.id, action);
60
63
  action.onClick?.();
61
- setIsMobileMenuOpen(false);
64
+ closeMobileMenu();
62
65
  };
63
66
  const handleMenuTriggerClick = () => {
64
67
  setIsMobileMenuOpen((current) => !current);
65
68
  onMenuTriggerClick?.();
66
69
  };
70
+ useEffect(() => {
71
+ if (!isMobileMenuOpen) {
72
+ return;
73
+ }
74
+ const handleEscape = (event) => {
75
+ if (event.key === "Escape") {
76
+ closeMobileMenu();
77
+ }
78
+ };
79
+ const previousOverflow = document.body.style.overflow;
80
+ document.body.style.overflow = "hidden";
81
+ window.addEventListener("keydown", handleEscape);
82
+ return () => {
83
+ document.body.style.overflow = previousOverflow;
84
+ window.removeEventListener("keydown", handleEscape);
85
+ };
86
+ }, [isMobileMenuOpen]);
67
87
  const navigationContent = (_jsx(_Fragment, { children: navigationItems.map((item) => (_jsx(HeaderNavigationLink, { activeItemId: resolvedActiveId, item: item, onSelect: handleNavigationSelect }, item.id))) }));
68
88
  const actionsContent = actions?.length ? (_jsx(_Fragment, { children: actions.map((action) => (_jsx(HeaderUtilityAction, { action: action, onAction: handleAction }, action.id))) })) : null;
69
- return (_jsxs("header", { ...props, className: getClassName(className), "data-mobile-menu-enabled": shouldShowMenuTrigger ? 'true' : undefined, style: style, children: [_jsxs("div", { className: "liquid-header-shell__inner", children: [_jsxs("div", { className: "liquid-header-shell__left", children: [shouldShowMenuTrigger ? (_jsx("button", { "aria-label": menuTriggerLabel, "aria-controls": mobileAccordionId, "aria-expanded": isMobileMenuOpen, className: "liquid-header-shell__action liquid-header-shell__menu-trigger", onClick: handleMenuTriggerClick, type: "button", children: _jsx(Icon, { name: "menu", size: 20 }) })) : null, _jsx("div", { className: "liquid-header-shell__brand", children: brand }), _jsx("nav", { "aria-label": navLabel, className: "liquid-header-shell__nav", children: _jsx("div", { className: "liquid-header-shell__nav-list", children: navigationContent }) })] }), actionsContent ? (_jsx("div", { className: "liquid-header-shell__actions", children: actionsContent })) : null, shouldShowMenuTrigger ? (_jsx("div", { "aria-hidden": "true", className: "liquid-header-shell__right-spacer" })) : null] }), shouldShowMenuTrigger ? (_jsxs("div", { className: "liquid-header-shell__mobile-accordion", hidden: !isMobileMenuOpen, id: mobileAccordionId, role: "region", children: [_jsx("nav", { "aria-label": navLabel, className: "liquid-header-shell__mobile-nav", children: _jsx("div", { className: "liquid-header-shell__mobile-nav-list", children: navigationContent }) }), actionsContent ? (_jsx("div", { className: "liquid-header-shell__mobile-actions", children: actionsContent })) : null] })) : null] }));
89
+ const mobileActionsContent = actions?.length ? (_jsx(_Fragment, { children: actions.map((action) => (_jsx(HeaderUtilityAction, { action: action, showLabel: true, onAction: handleAction }, `mobile:${action.id}`))) })) : null;
90
+ return (_jsxs("header", { ...props, className: getClassName(className), "data-mobile-menu-enabled": shouldShowMenuTrigger ? "true" : undefined, style: style, children: [_jsxs("div", { className: "liquid-header-shell__inner", children: [_jsxs("div", { className: "liquid-header-shell__left", children: [shouldShowMenuTrigger ? (_jsx("button", { "aria-label": menuTriggerLabel, "aria-controls": mobileAccordionId, "aria-expanded": isMobileMenuOpen, className: "liquid-header-shell__action liquid-header-shell__menu-trigger", onClick: handleMenuTriggerClick, type: "button", children: _jsx(Icon, { name: isMobileMenuOpen ? "close" : "menu", size: 20 }) })) : null, _jsx("div", { className: "liquid-header-shell__brand", children: brand }), _jsx("nav", { "aria-label": navLabel, className: "liquid-header-shell__nav", children: _jsx("div", { className: "liquid-header-shell__nav-list", children: navigationContent }) })] }), actionsContent ? (_jsx("div", { className: "liquid-header-shell__actions", children: actionsContent })) : null, shouldShowMenuTrigger ? (_jsx("div", { "aria-hidden": "true", className: "liquid-header-shell__right-spacer" })) : null] }), shouldShowMenuTrigger ? (_jsxs("div", { className: "liquid-header-shell__mobile-layer", "data-open": isMobileMenuOpen ? "true" : undefined, hidden: !isMobileMenuOpen, children: [_jsx("button", { "aria-label": "Close menu", className: "liquid-header-shell__mobile-backdrop", onClick: () => {
91
+ closeMobileMenu();
92
+ }, type: "button" }), _jsxs("aside", { className: "liquid-header-shell__mobile-panel", id: mobileAccordionId, role: "dialog", "aria-modal": "true", "aria-label": navLabel, onClick: (event) => {
93
+ const target = event.target;
94
+ if (target.closest(".liquid-header-shell__nav-link") ||
95
+ target.closest(".liquid-header-shell__action")) {
96
+ closeMobileMenu();
97
+ }
98
+ }, children: [_jsx("nav", { "aria-label": navLabel, className: "liquid-header-shell__mobile-nav", children: _jsx("div", { className: "liquid-header-shell__mobile-nav-list", children: navigationContent }) }), mobileActionsContent ? (_jsx("div", { className: "liquid-header-shell__mobile-actions", children: mobileActionsContent })) : null] })] })) : null] }));
70
99
  }