@thecb/components 11.2.14-beta.1 → 11.2.14-beta.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thecb/components",
3
- "version": "11.2.14-beta.1",
3
+ "version": "11.2.14-beta.3",
4
4
  "description": "Common lib for CityBase react components",
5
5
  "main": "dist/index.cjs.js",
6
6
  "typings": "dist/index.d.ts",
@@ -4,7 +4,6 @@ import { Box, Motion } from "../../atoms/layouts";
4
4
  import { fallbackValues } from "./NavMenu.theme.js";
5
5
  import { themeComponent } from "../../../util/themeUtils";
6
6
 
7
- // Animate with transforms (x) instead of left/right
8
7
  const menuVariants = {
9
8
  invisible: {
10
9
  x: "-100vw",
@@ -23,23 +22,22 @@ const menuVariants = {
23
22
  };
24
23
 
25
24
  const ImposterMenu = styled(Motion)`
26
- /* Full-screen fixed overlay */
25
+ /* Overlay starts below the header */
27
26
  position: fixed;
28
- top: 0;
27
+ top: ${({ headerSize }) => headerSize};
29
28
  left: 0;
30
29
  right: 0;
31
30
  bottom: 0;
32
- /* Semi-transparent gray overlay color */
31
+ /* Dim background for everything below the navbar */
33
32
  background: ${({ themeValues }) =>
34
33
  themeValues?.overlayColor || "rgba(0, 0, 0, 0.5)"};
35
- z-index: 9999; /* ensure it's above other content */
36
34
  `;
37
35
 
38
36
  const NavMenuMobile = ({
39
37
  id,
40
38
  menuContent = [],
41
39
  visible = false,
42
- headerSize = "72px",
40
+ headerSize = "74px",
43
41
  themeValues
44
42
  }) => {
45
43
  return (
@@ -48,6 +46,7 @@ const NavMenuMobile = ({
48
46
  variants={menuVariants}
49
47
  initial="invisible"
50
48
  animate={visible ? "visible" : "invisible"}
49
+ headerSize={headerSize}
51
50
  themeValues={themeValues}
52
51
  >
53
52
  {/*
@@ -58,10 +57,7 @@ const NavMenuMobile = ({
58
57
  width="100%"
59
58
  maxWidth="400px"
60
59
  padding="1rem 0.5rem"
61
- extraStyles={`
62
- position: relative;
63
- height: 100%;
64
- `}
60
+ extraStyles={`position: relative; max-width: 400px; height: calc(100vh - 72px);`}
65
61
  background={themeValues.backgroundColor}
66
62
  >
67
63
  {menuContent}
@@ -2,8 +2,20 @@ import { Canvas, Meta, Title, Story, Controls } from '@storybook/blocks';
2
2
 
3
3
  import * as NavMenuMobileStories from './NavMenuMobile.stories.js';
4
4
 
5
+ <Story />
6
+
5
7
  <Meta of={NavMenuMobileStories} />
6
8
 
7
9
  <Title />
8
10
 
9
- <Story />
11
+ The `NavMenuMobile` component is a responsive navigation menu designed specifically for mobile devices. It provides a clean and accessible interface for navigating through application sections. The menu can be toggled open or closed and supports customizable content and themes.
12
+
13
+ <Controls />
14
+
15
+
16
+ ### Features
17
+
18
+ - **Customizable Content**: The `menuContent` prop allows you to define the content displayed inside the menu.
19
+ - **Theming**: The component supports theming via the `themeValues` prop, enabling you to style the menu to match your application's design system.
20
+ - **Visibility Control**: The `visible` prop determines whether the menu is displayed, making it easy to toggle programmatically.
21
+ - **Header Positioning**: The `headerSize` prop ensures the menu is positioned correctly relative to the header.
@@ -1,9 +1,7 @@
1
1
  import React, { useState } from "react";
2
2
  import NavMenuMobile from "./NavMenuMobile";
3
- // import { Box, Button } from "../../layouts";
4
- import { Box } from "../../atoms/layouts";
5
- import { ButtonWithAction } from "../../atoms";
6
-
3
+ import { Box, Cluster } from "../../atoms/layouts";
4
+ import { HamburgerButton } from "../../atoms";
7
5
  import { fallbackValues } from "./NavMenu.theme";
8
6
 
9
7
  export default {
@@ -24,7 +22,7 @@ export default {
24
22
  </Box>
25
23
  ),
26
24
  visible: false,
27
- headerSize: "72px",
25
+ headerSize: "74px",
28
26
  themeValues: fallbackValues
29
27
  },
30
28
  argTypes: {
@@ -53,7 +51,7 @@ export default {
53
51
  description: "Height of the header, used to position the menu",
54
52
  table: {
55
53
  type: { summary: "string" },
56
- defaultValue: { summary: "72px" }
54
+ defaultValue: { summary: "74px" }
57
55
  }
58
56
  },
59
57
  themeValues: {
@@ -66,58 +64,36 @@ export default {
66
64
  }
67
65
  };
68
66
 
69
- export const ProfileMenu = {
67
+ export const BasicMenu = {
70
68
  args: {
71
- id: "profile-menu",
69
+ id: "basic-menu",
70
+ headerSize: "74px",
71
+ themeValues: fallbackValues,
72
72
  menuContent: (
73
- <Box>
74
- <p>Profile Item 1</p>
75
- <p>Profile Item 2</p>
76
- <p>Profile Item 3</p>
77
- </Box>
78
- ),
79
- visible: false,
80
- headerSize: "72px",
81
- themeValues: fallbackValues
82
- },
83
- render: args => {
84
- const [visible, setVisible] = useState(false);
85
-
86
- const toggleMenu = () => {
87
- setVisible(prev => !prev);
88
- };
89
-
90
- return (
91
73
  <>
92
- <ButtonWithAction onClick={toggleMenu}>
93
- {visible ? "Hide Menu" : "Show Menu"}
94
- </ButtonWithAction>
95
- <NavMenuMobile {...args} visible={visible} />
74
+ <p>Nav Item 1</p>
75
+ <p>Nav Item 2</p>
76
+ <p>Nav Item 3</p>
96
77
  </>
97
- );
98
- }
99
- };
100
-
101
- export const DefaultMenu = {
102
- args: {
103
- visible: true
104
- }
105
- };
106
-
107
- export const InteractiveMenu = {
78
+ )
79
+ },
108
80
  render: args => {
109
- const [visible, setVisible] = useState(false);
110
-
111
- const toggleMenu = () => {
112
- setVisible(prev => !prev);
113
- };
114
-
81
+ const [navMenuOpen, setNavMenuOpen] = useState(false);
115
82
  return (
116
83
  <>
117
- <ButtonWithAction onClick={toggleMenu}>
118
- {visible ? "Hide Menu" : "Show Menu"}
119
- </ButtonWithAction>
120
- <NavMenuMobile {...args} visible={visible} />
84
+ <Cluster
85
+ extraStyles="width: 100%; height: 100vh;"
86
+ justify="center"
87
+ align="center"
88
+ >
89
+ <HamburgerButton
90
+ isActive={navMenuOpen}
91
+ onClick={() => setNavMenuOpen(!navMenuOpen)}
92
+ activeColor={args.themeValues.backgroundColor.profile}
93
+ inactiveColor={args.themeValues.backgroundColor.profile}
94
+ />
95
+ </Cluster>
96
+ <NavMenuMobile {...args} visible={navMenuOpen} />
121
97
  </>
122
98
  );
123
99
  }
@@ -1,258 +0,0 @@
1
- import React, {
2
- useCallback,
3
- useContext,
4
- useEffect,
5
- useRef,
6
- useState
7
- } from "react";
8
- import { test } from "ramda";
9
- import {
10
- Box,
11
- Cluster,
12
- constants,
13
- Stack,
14
- Paragraph,
15
- ExternalLink,
16
- InternalLink,
17
- Switcher,
18
- withWindowSize
19
- } from "@thecb/components";
20
- import { ThemeContext } from "styled-components";
21
- import { URL_TEST } from "../../../constants/regex_constants";
22
- import HeaderItem from "./HeaderItem";
23
- import RightArrowIcon from "../../icons/RightArrowIcon";
24
- import NavLabel from "./NavLabel";
25
- import NavLink, { NavLinkLevels } from "./NavLink";
26
- import { getCallToActionInfo } from "../../../util/dataAdapters";
27
-
28
- const KEY_ARROW_DOWN = 40;
29
- const KEY_ARROW_UP = 38;
30
- const KEY_TAB = 9;
31
-
32
- const Link = ({ url, id, onSetRef, children }) => {
33
- return test(URL_TEST, url) ? (
34
- <ExternalLink href={url} ref={element => onSetRef(id, element)}>
35
- {children}
36
- </ExternalLink>
37
- ) : (
38
- <InternalLink to={url} ref={element => onSetRef(id, element)}>
39
- {children}
40
- </InternalLink>
41
- );
42
- };
43
-
44
- const LinkMenu = ({
45
- link,
46
- onChangeMenu,
47
- hamburgerMenuStyle,
48
- kbNavUsed,
49
- themeValues
50
- }) => {
51
- const { isMobile } = useContext(ThemeContext);
52
- const linkElements = useRef({});
53
- const [focusLink, setFocusLink] = useState(-1);
54
-
55
- const setElementFocus = key => {
56
- if (kbNavUsed) {
57
- linkElements.current[key].focus();
58
- }
59
- };
60
-
61
- const handleSetRef = useCallback((id, element) => {
62
- return (linkElements.current[id] = element);
63
- }, []);
64
-
65
- useEffect(() => {
66
- const ref1Key = Object.keys(linkElements.current)[0];
67
- setElementFocus(ref1Key);
68
- setFocusLink(0);
69
- }, []);
70
-
71
- const SmallLink = ({ text, showArrow = false }) => (
72
- <Cluster justify="flex-start" align="center">
73
- <Paragraph variant={isMobile ? "p" : "pS"} color={themeValues.linkColor}>
74
- {text}
75
- </Paragraph>
76
- {showArrow && <RightArrowIcon size={18} color={themeValues.linkColor} />}
77
- </Cluster>
78
- );
79
-
80
- const handleKeyDown = event => {
81
- switch (event.keyCode) {
82
- case KEY_TAB:
83
- event.preventDefault();
84
- onChangeMenu(event.shiftKey ? "previous" : "next");
85
- break;
86
- case KEY_ARROW_DOWN:
87
- case KEY_ARROW_UP:
88
- event.preventDefault();
89
- const increment = event.keyCode === KEY_ARROW_DOWN ? 1 : -1;
90
- const length = Object.keys(linkElements.current).length;
91
- const newFocus = (focusLink + (increment % length) + length) % length;
92
- const newFocusKey = Object.keys(linkElements.current)[newFocus];
93
- setFocusLink(newFocus);
94
- setElementFocus(newFocusKey);
95
- break;
96
- default:
97
- break;
98
- }
99
- };
100
-
101
- const renderSubLinkSectionName = subLink => {
102
- const { ctaUrl: subLinkUrl } = getCallToActionInfo(
103
- subLink.callToActionLink,
104
- "",
105
- subLink.nameUrl
106
- );
107
- if (hamburgerMenuStyle) {
108
- return subLinkUrl ? (
109
- <NavLink
110
- url={subLinkUrl}
111
- label={subLink.name}
112
- themeValues={themeValues}
113
- navLevel={NavLinkLevels.SUB_MENU}
114
- />
115
- ) : (
116
- <NavLabel text={subLink.name} themeValues={themeValues} navLevel={1} />
117
- );
118
- } else {
119
- return subLinkUrl ? (
120
- <Link url={subLinkUrl} id={subLink.id} onSetRef={handleSetRef}>
121
- <HeaderItem
122
- name={subLink?.name}
123
- showArrow={!hamburgerMenuStyle}
124
- themeValues={themeValues}
125
- />
126
- </Link>
127
- ) : (
128
- <HeaderItem
129
- name={subLink?.name}
130
- showArrow={false}
131
- themeValues={themeValues}
132
- />
133
- );
134
- }
135
- };
136
-
137
- const renderSubLinkLink = (smallLink, index) => {
138
- if (hamburgerMenuStyle) {
139
- return (
140
- <NavLink
141
- key={`navlink-${index}`}
142
- url={smallLink.externalUrl}
143
- label={smallLink.text}
144
- themeValues={themeValues}
145
- navLevel={NavLinkLevels.DETAIL}
146
- />
147
- );
148
- } else {
149
- return (
150
- <Box padding={"0"} key={`link-${index}`}>
151
- <Link
152
- url={smallLink.externalUrl}
153
- id={smallLink.id}
154
- onSetRef={handleSetRef}
155
- >
156
- <SmallLink text={smallLink.text} />
157
- </Link>
158
- </Box>
159
- );
160
- }
161
- };
162
-
163
- const { ctaUrl } = getCallToActionInfo(
164
- link.callToActionLink,
165
- "",
166
- link.nameUrl
167
- );
168
-
169
- return (
170
- <Box
171
- padding={hamburgerMenuStyle ? "16px 0" : "0"}
172
- background={
173
- hamburgerMenuStyle ? constants.colors.ATHENS_GREY : "transparent"
174
- }
175
- onKeyDown={handleKeyDown}
176
- extraStyles={
177
- hamburgerMenuStyle ? "margin-left: 16px; margin-right: 16px;" : ""
178
- }
179
- >
180
- <Switcher breakpoint="45rem" largeChild="1" largeChildSize="2">
181
- {hamburgerMenuStyle ? (
182
- <Box padding={hamburgerMenuStyle ? "0 16px 16px" : 0}>
183
- <Paragraph variant="pS" color={constants.colors.CHARADE_GREY}>
184
- {link?.description}
185
- </Paragraph>
186
- </Box>
187
- ) : (
188
- <Box key="title-desc" padding={"0 16px 0 4px"}>
189
- <Stack childGap="8px">
190
- <Box padding="0">
191
- {ctaUrl ? (
192
- <Box padding="0">
193
- <Link url={ctaUrl} id={link.id} onSetRef={handleSetRef}>
194
- <HeaderItem
195
- name={link?.name}
196
- showArrow={!hamburgerMenuStyle}
197
- themeValues={themeValues}
198
- />
199
- </Link>
200
- </Box>
201
- ) : (
202
- <HeaderItem
203
- name={link?.name}
204
- showArrow={ctaUrl !== ""}
205
- themeValues={themeValues}
206
- />
207
- )}
208
- </Box>
209
- <Box padding={"16px 24px 16px 0"}>
210
- <Paragraph variant="pS">{link?.description}</Paragraph>
211
- </Box>
212
- </Stack>
213
- </Box>
214
- )}
215
- {link?.linkLists?.map((subLink, index) => (
216
- <Box
217
- padding={hamburgerMenuStyle ? "0" : "0 24px"}
218
- key={`subLink-${index}`}
219
- >
220
- <Stack childGap={"1rem"}>
221
- {renderSubLinkSectionName(subLink)}
222
- {subLink?.links?.map((smallLink, index) =>
223
- renderSubLinkLink(smallLink, index)
224
- )}
225
- </Stack>
226
- </Box>
227
- ))}
228
- <Box padding={hamburgerMenuStyle ? "16px 0" : "0 24px"}>
229
- <Stack childGap={"1rem"}>
230
- <Box padding={"0"}>
231
- {hamburgerMenuStyle ? (
232
- <NavLabel
233
- text={"Featured Services"}
234
- themeValues={themeValues}
235
- navLevel={1}
236
- />
237
- ) : (
238
- <HeaderItem
239
- name="Featured Services"
240
- showArrow={false}
241
- themeValues={themeValues}
242
- color={themeValues.linkColor}
243
- weight={constants.fontWeights.FONT_WEIGHT_SEMIBOLD}
244
- variant="p"
245
- />
246
- )}
247
- </Box>
248
- {link?.featuredLinks?.links?.map((featuredLink, index) =>
249
- renderSubLinkLink(featuredLink, index)
250
- )}
251
- </Stack>
252
- </Box>
253
- </Switcher>
254
- </Box>
255
- );
256
- };
257
-
258
- export default withWindowSize(LinkMenu);
@@ -1,138 +0,0 @@
1
- import React, { useContext } from "react";
2
- import { Box, Cover, Heading, Motion, withWindowSize } from "@thecb/components";
3
- import { ThemeContext } from "styled-components";
4
- import LinkMenu from "./LinkMenu";
5
- import HamburgerNavSection from "./HamburgerNavSection";
6
- import { borderWrapper } from "./style";
7
-
8
- const NavLinks = ({
9
- navigation,
10
- hamburgerMenuStyle,
11
- themeValues,
12
- navMenuOpen,
13
- hamburgerOpenSection,
14
- selectedNavMenu,
15
- setSearchMenuOpen,
16
- setSelectedMenu,
17
- setNavMenuOpen,
18
- setIsAnimating,
19
- setKeyboardNavUsed,
20
- setHamburgerOpenSection
21
- }) => {
22
- const { isMobile } = useContext(ThemeContext);
23
- const selectMenu = (index, kbNavUsed) => {
24
- setKeyboardNavUsed(kbNavUsed);
25
- if (navMenuOpen !== true) {
26
- setSearchMenuOpen(false);
27
- setSelectedMenu(index);
28
- setNavMenuOpen(true);
29
- setIsAnimating(true);
30
- setTimeout(() => {
31
- setIsAnimating(false);
32
- }, 600);
33
- } else if (selectedNavMenu !== index) {
34
- setSelectedMenu(index);
35
- setSearchMenuOpen(false);
36
- }
37
- };
38
-
39
- return (
40
- <>
41
- {navigation?.sections?.map((link, index) => {
42
- debugger;
43
- return (
44
- <Box
45
- padding="0"
46
- key={`nav-link-${index}`}
47
- extraStyles={!hamburgerMenuStyle && `height: 102px;`}
48
- >
49
- <Cover
50
- singleChild
51
- minHeight={isMobile ? "0%" : "100%"}
52
- key={`section-${index}`}
53
- >
54
- {hamburgerMenuStyle ? (
55
- <HamburgerNavSection
56
- link={link}
57
- toggleSection={() => {
58
- link?.id === hamburgerOpenSection
59
- ? setHamburgerOpenSection("")
60
- : setHamburgerOpenSection(link?.id);
61
- }}
62
- isOpen={hamburgerOpenSection === link?.id}
63
- name={link?.name}
64
- themeValues={themeValues}
65
- >
66
- <Motion
67
- padding="0"
68
- transition={{ duration: 0.3 }}
69
- positionTransition
70
- extraStyles={`transform-origin: 100% 0;`}
71
- >
72
- <LinkMenu
73
- key={link.id}
74
- link={link}
75
- hamburgerMenuStyle={hamburgerMenuStyle}
76
- themeValues={themeValues}
77
- kbNavUsed={false}
78
- onChangeMenu={() => {
79
- setNavMenuOpen(false);
80
- setSelectedMenu(100);
81
- }}
82
- />
83
- </Motion>
84
- </HamburgerNavSection>
85
- ) : (
86
- <Box
87
- padding="0"
88
- as="button"
89
- background="transparent"
90
- tabIndex="0"
91
- onClick={() => {
92
- setSearchMenuOpen(false);
93
- setSelectedMenu(index);
94
- setNavMenuOpen(!navMenuOpen);
95
- setIsAnimating(true);
96
- setTimeout(() => {
97
- setIsAnimating(false);
98
- }, 600);
99
- }}
100
- onMouseEnter={() => selectMenu(index, false)}
101
- onFocus={() => selectMenu(index, true)}
102
- >
103
- <Box padding="0 1.5rem">
104
- <Heading
105
- variant="h6"
106
- color={isMobile ? "#FFFFFF" : themeValues.linkColor}
107
- extraStyles={
108
- isMobile ? "font-size: 14px;" : `font-size: 16px;`
109
- }
110
- >
111
- {link?.name}
112
- </Heading>
113
- </Box>
114
- </Box>
115
- )}
116
- {!hamburgerMenuStyle && (
117
- <Motion
118
- padding="0"
119
- minWidth="100%"
120
- variants={borderWrapper}
121
- animate={navMenuOpen ? "open" : "closed"}
122
- layoutTransition
123
- extraStyles={`border-bottom: 3px solid ${
124
- selectedNavMenu === index
125
- ? `${themeValues.linkColor}`
126
- : `transparent`
127
- }`}
128
- />
129
- )}
130
- </Cover>
131
- </Box>
132
- );
133
- })}
134
- </>
135
- );
136
- };
137
-
138
- export default NavLinks;