@redocly/theme 0.9.10 → 0.9.11

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.
@@ -52,7 +52,13 @@ const ProfileWrapper = styled_components_1.default.div.attrs(() => ({
52
52
  padding: 0 var(--navbar-item-padding-horizontal);
53
53
  `;
54
54
  const StyledUserName = styled_components_1.default.span `
55
- color: ${({ color }) => color || 'var(--navbar-text-color)'};
55
+ font-family: var(--profile-name-font-family);
56
+ font-size: var(--profile-name-font-size);
57
+ font-weight: var(--profile-name-font-weight);
58
+ color: var(--profile-name-text-color);
59
+ padding: var(--profile-name-padding-vertical) var(--profile-name-padding-horizontal);
60
+ margin: 0 var(--profile-name-margin-horizontal);
61
+
56
62
  display: none;
57
63
 
58
64
  ${({ theme }) => { var _a; return (_a = theme.mediaQueries) === null || _a === void 0 ? void 0 : _a.medium; }} {
@@ -60,19 +66,23 @@ const StyledUserName = styled_components_1.default.span `
60
66
  }
61
67
  `;
62
68
  const AvatarWrapper = styled_components_1.default.div `
63
- width: 40px;
64
- height: 40px;
69
+ width: var(--profile-avatar-width);
70
+ height: var(--profile-avatar-height);
65
71
  display: flex;
66
72
  overflow: hidden;
67
73
  position: relative;
68
- font-size: 1.25rem;
69
74
  align-items: center;
70
75
  flex-shrink: 0;
71
- line-height: 1;
72
- user-select: none;
73
- border-radius: 50%;
74
76
  justify-content: center;
75
- margin-left: 16px;
77
+ user-select: none;
78
+
79
+ font-family: var(--profile-avatar-font-family);
80
+ font-size: var(--profile-avatar-font-size);
81
+ line-height: var(--profile-avatar-line-height);
82
+ font-weight: var(--profile-avatar-font-weight);
83
+
84
+ border-radius: var(--profile-avatar-border-radius);
85
+ margin-left: var(--profile-avatar-margin-left);
76
86
 
77
87
  ${({ background }) => (0, styled_components_1.css) `
78
88
  background-color: ${background};
@@ -6,7 +6,7 @@ export interface UserProfileProps {
6
6
  picture: string;
7
7
  logoutDisabled?: boolean;
8
8
  };
9
- handleLogout: (logoutRedirect?: string) => void;
9
+ handleLogout: () => void;
10
10
  hasDeveloperOnboarding?: boolean;
11
11
  }
12
12
  export declare function UserProfile({ userInfo, handleLogout, hasDeveloperOnboarding, }: UserProfileProps): JSX.Element;
@@ -29,18 +29,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.UserProfile = void 0;
30
30
  const react_1 = __importStar(require("react"));
31
31
  const styled_components_1 = __importDefault(require("styled-components"));
32
- const Link_1 = require("../mocks/Link");
33
32
  const Profile_1 = require("../Profile/Profile");
34
33
  const Tooltip_1 = require("../Tooltip/Tooltip");
35
- const useThemeConfig_1 = require("../hooks/useThemeConfig");
34
+ const UserProfileMenu_1 = require("./UserProfileMenu");
36
35
  function UserProfile({ userInfo, handleLogout, hasDeveloperOnboarding = false, }) {
37
36
  const [isOpened, setIsOpened] = (0, react_1.useState)(false);
38
- const { userProfile: userProfileSettings } = (0, useThemeConfig_1.useThemeConfig)();
39
- const logoutRedirect = (userProfileSettings === null || userProfileSettings === void 0 ? void 0 : userProfileSettings.logoutRedirect) || '/';
40
- return (react_1.default.createElement(StyledTooltip, { isOpen: isOpened, withArrow: false, className: "copy-button", placement: "bottom", width: "100%", tip: react_1.default.createElement(StyledUl, { onClick: () => setIsOpened(false) },
41
- hasDeveloperOnboarding ? (react_1.default.createElement(Link_1.Link, { to: "/apps" },
42
- react_1.default.createElement(StyledLi, null, "My Apps"))) : null,
43
- react_1.default.createElement(StyledLi, { onClick: () => handleLogout(logoutRedirect) }, (userProfileSettings === null || userProfileSettings === void 0 ? void 0 : userProfileSettings.logoutLabel) || 'Log out')) },
37
+ return (react_1.default.createElement(StyledTooltip, { isOpen: isOpened, withArrow: false, className: "copy-button", placement: "bottom", width: "100%", tip: react_1.default.createElement(UserProfileMenu_1.UserProfileMenu, { hasDeveloperOnboarding: hasDeveloperOnboarding, handleLogout: handleLogout, setIsOpened: setIsOpened }) },
44
38
  react_1.default.createElement(Profile_1.Profile, { name: userInfo.name, imageUrl: userInfo.picture, onClick: userInfo.logoutDisabled ? undefined : () => setIsOpened(!isOpened) })));
45
39
  }
46
40
  exports.UserProfile = UserProfile;
@@ -49,32 +43,4 @@ const StyledTooltip = (0, styled_components_1.default)(Tooltip_1.Tooltip) `
49
43
  padding: 0;
50
44
  }
51
45
  `;
52
- const StyledUl = styled_components_1.default.ul `
53
- margin: 0;
54
- padding: 0;
55
- list-style: none;
56
- text-align: left;
57
- background-color: var(--search-modal-background);
58
- color: var(--search-modal-text-color);
59
- min-width: 100px;
60
- a {
61
- text-decoration: none;
62
- &:hover {
63
- color: inherit;
64
- }
65
- &:visited {
66
- color: inherit;
67
- }
68
- }
69
- `;
70
- const StyledLi = styled_components_1.default.li `
71
- cursor: pointer;
72
- font-size: 16px;
73
- list-style: none;
74
- padding: 15px 20px;
75
- transition: background-color 0.25s ease 0s;
76
- &:hover {
77
- background-color: rgba(0, 0, 0, 0.1);
78
- }
79
- `;
80
46
  //# sourceMappingURL=UserProfile.js.map
@@ -0,0 +1,8 @@
1
+ /// <reference types="react" />
2
+ interface UserProfileMenuProps {
3
+ setIsOpened: (isOpen: boolean) => void;
4
+ handleLogout: () => void;
5
+ hasDeveloperOnboarding: boolean;
6
+ }
7
+ export declare function UserProfileMenu({ hasDeveloperOnboarding, setIsOpened, handleLogout, }: UserProfileMenuProps): JSX.Element;
8
+ export {};
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.UserProfileMenu = void 0;
30
+ const react_1 = __importDefault(require("react"));
31
+ const styled_components_1 = __importStar(require("styled-components"));
32
+ const Link_1 = require("../mocks/Link");
33
+ const hooks_1 = require("../hooks");
34
+ function UserProfileMenu({ hasDeveloperOnboarding, setIsOpened, handleLogout, }) {
35
+ var _a;
36
+ const { userProfile } = (0, hooks_1.useThemeConfig)();
37
+ return (react_1.default.createElement(react_1.default.Fragment, null,
38
+ react_1.default.createElement(StyledUl, { onClick: () => setIsOpened(false) },
39
+ hasDeveloperOnboarding ? (react_1.default.createElement(Link_1.Link, { to: "/apps" },
40
+ react_1.default.createElement(StyledLi, null, "My Apps"))) : null, (_a = userProfile === null || userProfile === void 0 ? void 0 : userProfile.menu) === null || _a === void 0 ? void 0 :
41
+ _a.map((item) => (react_1.default.createElement(Link_1.Link, { external: item.external, key: item.label, to: item.link || '' },
42
+ react_1.default.createElement(StyledLi, { separatorLine: item === null || item === void 0 ? void 0 : item.separatorLine }, item.label)))),
43
+ react_1.default.createElement(StyledLi, { onClick: () => handleLogout() }, (userProfile === null || userProfile === void 0 ? void 0 : userProfile.logoutLabel) || 'Log out'))));
44
+ }
45
+ exports.UserProfileMenu = UserProfileMenu;
46
+ const StyledUl = styled_components_1.default.ul `
47
+ margin: var(--profile-menu-margin);
48
+ padding: var(--profile-menu-padding);
49
+ list-style: none;
50
+ background-color: var(--profile-menu-background-color);
51
+ min-width: 100px;
52
+ a {
53
+ text-decoration: none;
54
+ }
55
+ `;
56
+ const StyledLi = styled_components_1.default.li `
57
+ cursor: pointer;
58
+ font-size: var(--profile-menu-item-font-size);
59
+ font-family: var(--profile-menu-item-font-family);
60
+ font-weight: var(--profile-menu-item-font-weight);
61
+ line-height: var(--profile-menu-item-line-height);
62
+ color: var(--profile-menu-item-text-color);
63
+ text-align: var(--profile-menu-item-text-align);
64
+
65
+ list-style: none;
66
+
67
+ padding: var(--profile-menu-item-padding-vertical) var(--profile-menu-item-padding-horizontal);
68
+
69
+ transition: background-color 0.25s ease 0s;
70
+
71
+ &:hover {
72
+ color: var(--profile-menu-item-hover-text-color);
73
+ background-color: var(--profile-menu-item-hover-background-color);
74
+ text-decoration: var(--profile-menu-item-hover-text-decoration);
75
+ }
76
+ &:active {
77
+ color: var(--profile-menu-item-active-text-color);
78
+ background-color: var(--profile-menu-item-active-background-color);
79
+ text-decoration: var(--profile-menu-item-active-text-decoration);
80
+ }
81
+
82
+ ${({ separatorLine }) => separatorLine &&
83
+ (0, styled_components_1.css) `
84
+ border-bottom: 1px solid var(--profile-menu-item-separator-line-color);
85
+ `}
86
+ `;
87
+ //# sourceMappingURL=UserProfileMenu.js.map
package/lib/config.d.ts CHANGED
@@ -648,17 +648,17 @@ export declare const ThemeConfig: z.ZodDefault<z.ZodObject<{
648
648
  userProfile: z.ZodDefault<z.ZodOptional<z.ZodObject<{
649
649
  loginLabel: z.ZodOptional<z.ZodDefault<z.ZodString>>;
650
650
  logoutLabel: z.ZodOptional<z.ZodDefault<z.ZodString>>;
651
- logoutRedirect: z.ZodOptional<z.ZodDefault<z.ZodString>>;
651
+ menu: z.ZodOptional<z.ZodArray<z.ZodAny, "many">>;
652
652
  hide: z.ZodOptional<z.ZodBoolean>;
653
653
  }, "strip", z.ZodTypeAny, {
654
654
  loginLabel?: string | undefined;
655
655
  logoutLabel?: string | undefined;
656
- logoutRedirect?: string | undefined;
656
+ menu?: any[] | undefined;
657
657
  hide?: boolean | undefined;
658
658
  }, {
659
659
  loginLabel?: string | undefined;
660
660
  logoutLabel?: string | undefined;
661
- logoutRedirect?: string | undefined;
661
+ menu?: any[] | undefined;
662
662
  hide?: boolean | undefined;
663
663
  }>>>;
664
664
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
@@ -1229,17 +1229,17 @@ export declare const ThemeConfig: z.ZodDefault<z.ZodObject<{
1229
1229
  userProfile: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1230
1230
  loginLabel: z.ZodOptional<z.ZodDefault<z.ZodString>>;
1231
1231
  logoutLabel: z.ZodOptional<z.ZodDefault<z.ZodString>>;
1232
- logoutRedirect: z.ZodOptional<z.ZodDefault<z.ZodString>>;
1232
+ menu: z.ZodOptional<z.ZodArray<z.ZodAny, "many">>;
1233
1233
  hide: z.ZodOptional<z.ZodBoolean>;
1234
1234
  }, "strip", z.ZodTypeAny, {
1235
1235
  loginLabel?: string | undefined;
1236
1236
  logoutLabel?: string | undefined;
1237
- logoutRedirect?: string | undefined;
1237
+ menu?: any[] | undefined;
1238
1238
  hide?: boolean | undefined;
1239
1239
  }, {
1240
1240
  loginLabel?: string | undefined;
1241
1241
  logoutLabel?: string | undefined;
1242
- logoutRedirect?: string | undefined;
1242
+ menu?: any[] | undefined;
1243
1243
  hide?: boolean | undefined;
1244
1244
  }>>>;
1245
1245
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
@@ -1810,17 +1810,17 @@ export declare const ThemeConfig: z.ZodDefault<z.ZodObject<{
1810
1810
  userProfile: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1811
1811
  loginLabel: z.ZodOptional<z.ZodDefault<z.ZodString>>;
1812
1812
  logoutLabel: z.ZodOptional<z.ZodDefault<z.ZodString>>;
1813
- logoutRedirect: z.ZodOptional<z.ZodDefault<z.ZodString>>;
1813
+ menu: z.ZodOptional<z.ZodArray<z.ZodAny, "many">>;
1814
1814
  hide: z.ZodOptional<z.ZodBoolean>;
1815
1815
  }, "strip", z.ZodTypeAny, {
1816
1816
  loginLabel?: string | undefined;
1817
1817
  logoutLabel?: string | undefined;
1818
- logoutRedirect?: string | undefined;
1818
+ menu?: any[] | undefined;
1819
1819
  hide?: boolean | undefined;
1820
1820
  }, {
1821
1821
  loginLabel?: string | undefined;
1822
1822
  logoutLabel?: string | undefined;
1823
- logoutRedirect?: string | undefined;
1823
+ menu?: any[] | undefined;
1824
1824
  hide?: boolean | undefined;
1825
1825
  }>>>;
1826
1826
  }, z.ZodTypeAny, "passthrough">>>;
package/lib/config.js CHANGED
@@ -205,7 +205,7 @@ exports.ThemeConfig = zod_1.z
205
205
  .object({
206
206
  loginLabel: zod_1.z.string().default('Login').optional(),
207
207
  logoutLabel: zod_1.z.string().default('Logout').optional(),
208
- logoutRedirect: zod_1.z.string().default('/').optional(),
208
+ menu: zod_1.z.array(zod_1.z.any()).optional(), // should be same as navbar items type
209
209
  })
210
210
  .extend(HideConfig.shape)
211
211
  .optional()
@@ -1939,6 +1939,66 @@ const pages = (0, styled_components_1.css) `
1939
1939
 
1940
1940
  // @tokens End
1941
1941
  `;
1942
+ const userProfile = (0, styled_components_1.css) `
1943
+
1944
+ /**
1945
+ * @tokens User Profile
1946
+ */
1947
+
1948
+ --profile-name-font-family: var(--navbar-item-font-family); // @presenter FontFamily
1949
+ --profile-name-font-size: var(--navbar-item-font-size); // @presenter FontSize
1950
+ --profile-name-font-weight: var(--font-weight-regular); // @presenter FontWeight
1951
+ --profile-name-line-height: var(--line-height-base); // @presenter LineHeight
1952
+ --profile-name-text-color: var(--navbar-text-color); // @presenter Color
1953
+
1954
+ --profile-name-padding-horizontal: 0; // @presenter Spacing
1955
+ --profile-name-padding-vertical: 0; // @presenter Spacing
1956
+ --profile-name-margin-horizontal: 0; // @presenter Spacing
1957
+
1958
+ --profile-avatar-width: 40px;
1959
+ --profile-avatar-height: 40px;
1960
+ --profile-avatar-border-radius: 50%; // @presenter BorderRadius
1961
+
1962
+ --profile-avatar-font-family: var(--navbar-item-font-family); // @presenter FontFamily
1963
+ --profile-avatar-font-size: var(--navbar-item-font-size) // @presenter FontSize
1964
+ --profile-avatar-font-weight: var(--font-weight-regular); // @presenter FontWeight
1965
+ --profile-avatar-line-height: var(--line-height-base); // @presenter LineHeight
1966
+
1967
+ --profile-avatar-margin-left: 16px; // @presenter Spacing
1968
+ --profile-avatar-padding-vertical: 16px; // @presenter Spacing
1969
+ --profile-avatar-padding-horizontal: 16px; // @presenter Spacing
1970
+
1971
+
1972
+ /**
1973
+ * @tokens User Profile Menu
1974
+ */
1975
+
1976
+ --profile-menu-background-color: var(--background-color); // @presenter Color
1977
+ --profile-menu-padding: 0px; // @presenter Spacing
1978
+ --profile-menu-margin: 0px; // @presenter Spacing
1979
+
1980
+ --profile-menu-item-font-family: var(--navbar-item-font-family); // @presenter FontFamily
1981
+ --profile-menu-item-text-color: var(--text-color); // @presenter Color
1982
+ --profile-menu-item-font-size: var(--navbar-item-font-size); // @presenter FontSize
1983
+ --profile-menu-item-font-weight: var(--font-weight-regular); // @presenter FontWeight
1984
+ --profile-menu-item-line-height: var(--line-height-base); // @presenter LineHeight
1985
+ --profile-menu-item-text-align: left;
1986
+
1987
+ --profile-menu-item-padding-horizontal: 15px; // @presenter Spacing
1988
+ --profile-menu-item-padding-vertical: 20px; // @presenter Spacing
1989
+
1990
+ --profile-menu-item-hover-background-color: var(--color-primary-300); // @presenter Color
1991
+ --profile-menu-item-active-background-color: var(--color-primary-300); // @presenter Color
1992
+ --profile-menu-item-hover-text-color: var(--navbar-text-color); // @presenter Color
1993
+ --profile-menu-item-hover-text-decoration: none;
1994
+ --profile-menu-item-active-text-color: var(--navbar-text-color); // @presenter Color
1995
+ --profile-menu-item-active-text-decoration: none;
1996
+
1997
+ --profile-menu-item-separator-line-color: var(--border-color); // @presenter Color
1998
+
1999
+ // @tokens End
2000
+
2001
+ `;
1942
2002
  const modal = (0, styled_components_1.css) `
1943
2003
  body:has(.modal) {
1944
2004
  overflow: hidden;
@@ -1978,6 +2038,7 @@ exports.styles = (0, styled_components_1.css) `
1978
2038
  ${loadProgressBar}
1979
2039
  ${pages}
1980
2040
  ${modal}
2041
+ ${userProfile}
1981
2042
 
1982
2043
  --api-onboarding-table-text-color: #4e5356;
1983
2044
  --api-catalog-card-min-width: 250px;
@@ -1,3 +1,3 @@
1
- import type { Location } from 'history';
1
+ import type { Location } from 'react-router-dom';
2
2
  export type UseActiveSectionIdReturnType = string;
3
- export declare function useActiveSectionId(location: Location, hasOverviewPage?: boolean): UseActiveSectionIdReturnType;
3
+ export declare function useActiveSectionId(location: Location, hasOverviewPage?: boolean, withNavbar?: boolean): UseActiveSectionIdReturnType;
@@ -7,9 +7,10 @@ exports.useActiveSectionId = void 0;
7
7
  const react_1 = require("react");
8
8
  const lodash_throttle_1 = __importDefault(require("lodash.throttle"));
9
9
  const useNavbarHeight_1 = require("../hooks/useNavbarHeight");
10
- function useActiveSectionId(location, hasOverviewPage = false) {
10
+ function useActiveSectionId(location, hasOverviewPage = false, withNavbar = true) {
11
11
  const [itemId, setItemId] = (0, react_1.useState)('');
12
12
  const navbarHeight = (0, useNavbarHeight_1.useNavbarHeight)(location);
13
+ const heightOffset = (withNavbar ? navbarHeight : 0) + 5; // use small padding 5px to account for rounding;
13
14
  const scrollListener = (0, react_1.useMemo)(() => (0, lodash_throttle_1.default)(() => {
14
15
  const sections = document.querySelectorAll('[data-section-id]');
15
16
  if (sections.length < 2) {
@@ -19,8 +20,7 @@ function useActiveSectionId(location, hasOverviewPage = false) {
19
20
  for (let i = 0; i < sections.length; i++) {
20
21
  const section = sections[i];
21
22
  const rect = section.getBoundingClientRect();
22
- if (rect.y < navbarHeight + 5 && rect.bottom > navbarHeight + 5) {
23
- // use small padding 5px to account for rounding
23
+ if (rect.y < heightOffset && rect.bottom > heightOffset) {
24
24
  setItemId(section.getAttribute('data-section-id') || '');
25
25
  return;
26
26
  }
@@ -28,7 +28,7 @@ function useActiveSectionId(location, hasOverviewPage = false) {
28
28
  if (hasOverviewPage) {
29
29
  setItemId('');
30
30
  }
31
- }, 150), [navbarHeight, hasOverviewPage]);
31
+ }, 150), [heightOffset, hasOverviewPage]);
32
32
  (0, react_1.useEffect)(() => {
33
33
  window.addEventListener('scroll', scrollListener, { capture: false });
34
34
  setTimeout(() => {
@@ -37,7 +37,7 @@ function useActiveSectionId(location, hasOverviewPage = false) {
37
37
  return () => {
38
38
  window.removeEventListener('scroll', scrollListener);
39
39
  };
40
- }, [location, navbarHeight, scrollListener]);
40
+ }, [location, heightOffset, scrollListener]);
41
41
  return itemId;
42
42
  }
43
43
  exports.useActiveSectionId = useActiveSectionId;
@@ -1,3 +1,3 @@
1
- import type { Location } from 'history';
1
+ import type { Location } from 'react-router-dom';
2
2
  export type UseNavbarHeightReturnType = number;
3
3
  export declare function useNavbarHeight(location: Location): UseNavbarHeightReturnType;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.9.10",
3
+ "version": "0.9.11",
4
4
  "description": "Shared UI components lib",
5
5
  "author": "team@redocly.com",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -15,25 +15,6 @@
15
15
  "./*": "./lib/index.js",
16
16
  "./src/": "./src/"
17
17
  },
18
- "scripts": {
19
- "start": "npm-run-all --parallel storybook storybook:tokens:watch",
20
- "watch": "tsc -p tsconfig.build.json && (concurrently \"tsc -w -p tsconfig.build.json\" \"tsc-alias -w -p tsconfig.build.json\")",
21
- "ts:check": "tsc --noEmit --skipLibCheck",
22
- "clean": "rm -rf lib",
23
- "compile": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
24
- "build": "npm run clean && npm run compile",
25
- "test": "jest",
26
- "test:update": "jest -u",
27
- "test:watch": "jest --watch",
28
- "test:coverage": "jest --coverage",
29
- "test:coverage:html": "jest --coverage --coverageReporters html",
30
- "e2e": "npx chromatic -b storybook:build --auto-accept-changes main --exit-once-uploaded",
31
- "storybook": "start-storybook -p 6006",
32
- "storybook:build": "npm run storybook:tokens && build-storybook",
33
- "storybook:tokens": "ts-node scripts/generate-css-tokens.ts",
34
- "storybook:tokens:watch": "ts-node-dev --respawn scripts/generate-css-tokens.ts",
35
- "chromatic": "chromatic --exit-zero-on-changes"
36
- },
37
18
  "peerDependencies": {
38
19
  "lodash.throttle": "^4.1.1",
39
20
  "prismjs": "^1.28.0",
@@ -72,6 +53,7 @@
72
53
  "@types/react-dom": "^17.0.11",
73
54
  "@types/styled-components": "^5.1.26",
74
55
  "@types/styled-system": "^5.1.13",
56
+ "@types/testing-library__jest-dom": "^5.14.5",
75
57
  "@typescript-eslint/eslint-plugin": "^5.23.0",
76
58
  "@typescript-eslint/parser": "^5.23.0",
77
59
  "chromatic": "^6.10.2",
@@ -86,7 +68,7 @@
86
68
  "react-refresh": "^0.14.0",
87
69
  "react-router-dom": "^6.4.4",
88
70
  "storybook-addon-pseudo-states": "^1.15.1",
89
- "storybook-design-token": "^2.7.1",
71
+ "storybook-design-token": "^2.9.0",
90
72
  "styled-components": "^5.3.6",
91
73
  "styled-system": "^5.1.5",
92
74
  "ts-jest": "^29.0.3",
@@ -124,5 +106,24 @@
124
106
  ]
125
107
  }
126
108
  }
109
+ },
110
+ "scripts": {
111
+ "start": "npm-run-all --parallel storybook storybook:tokens:watch",
112
+ "watch": "tsc -p tsconfig.build.json && (concurrently \"tsc -w -p tsconfig.build.json\" \"tsc-alias -w -p tsconfig.build.json\")",
113
+ "ts:check": "tsc --noEmit --skipLibCheck",
114
+ "clean": "rm -rf lib",
115
+ "compile": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
116
+ "build": "npm run clean && npm run compile",
117
+ "test": "jest",
118
+ "test:update": "jest -u",
119
+ "test:watch": "jest --watch",
120
+ "test:coverage": "jest --coverage",
121
+ "test:coverage:html": "jest --coverage --coverageReporters html",
122
+ "e2e": "npx chromatic -b storybook:build --auto-accept-changes main --exit-once-uploaded",
123
+ "storybook": "start-storybook -p 6006",
124
+ "storybook:build": "npm run storybook:tokens && build-storybook",
125
+ "storybook:tokens": "ts-node scripts/generate-css-tokens.ts",
126
+ "storybook:tokens:watch": "ts-node-dev --respawn scripts/generate-css-tokens.ts",
127
+ "chromatic": "chromatic --exit-zero-on-changes"
127
128
  }
128
- }
129
+ }
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
- import type { ResolvedNavItem } from '@redocly/theme/src/types/portal';
4
+ import type { ResolvedNavItem } from '../types/portal';
5
5
 
6
6
  import type { CatalogConfig } from '@theme/types/portal/src/shared/types/catalog';
7
7
  import { usePageSharedData } from '@portal/hooks/index.js';
@@ -55,7 +55,13 @@ const ProfileWrapper = styled.div.attrs(() => ({
55
55
  `;
56
56
 
57
57
  const StyledUserName = styled.span`
58
- color: ${({ color }) => color || 'var(--navbar-text-color)'};
58
+ font-family: var(--profile-name-font-family);
59
+ font-size: var(--profile-name-font-size);
60
+ font-weight: var(--profile-name-font-weight);
61
+ color: var(--profile-name-text-color);
62
+ padding: var(--profile-name-padding-vertical) var(--profile-name-padding-horizontal);
63
+ margin: 0 var(--profile-name-margin-horizontal);
64
+
59
65
  display: none;
60
66
 
61
67
  ${({ theme }) => theme.mediaQueries?.medium} {
@@ -64,19 +70,23 @@ const StyledUserName = styled.span`
64
70
  `;
65
71
 
66
72
  const AvatarWrapper = styled.div<{ background?: string }>`
67
- width: 40px;
68
- height: 40px;
73
+ width: var(--profile-avatar-width);
74
+ height: var(--profile-avatar-height);
69
75
  display: flex;
70
76
  overflow: hidden;
71
77
  position: relative;
72
- font-size: 1.25rem;
73
78
  align-items: center;
74
79
  flex-shrink: 0;
75
- line-height: 1;
76
- user-select: none;
77
- border-radius: 50%;
78
80
  justify-content: center;
79
- margin-left: 16px;
81
+ user-select: none;
82
+
83
+ font-family: var(--profile-avatar-font-family);
84
+ font-size: var(--profile-avatar-font-size);
85
+ line-height: var(--profile-avatar-line-height);
86
+ font-weight: var(--profile-avatar-font-weight);
87
+
88
+ border-radius: var(--profile-avatar-border-radius);
89
+ margin-left: var(--profile-avatar-margin-left);
80
90
 
81
91
  ${({ background }) => css`
82
92
  background-color: ${background};
@@ -1,10 +1,10 @@
1
1
  import React, { useState } from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
- import { Link } from '@portal/Link';
5
4
  import { Profile } from '@theme/Profile/Profile';
6
5
  import { Tooltip } from '@theme/Tooltip/Tooltip';
7
- import { useThemeConfig } from '@theme/hooks/useThemeConfig';
6
+
7
+ import { UserProfileMenu } from './UserProfileMenu';
8
8
 
9
9
  export interface UserProfileProps {
10
10
  userInfo: {
@@ -13,7 +13,7 @@ export interface UserProfileProps {
13
13
  picture: string;
14
14
  logoutDisabled?: boolean;
15
15
  };
16
- handleLogout: (logoutRedirect?: string) => void;
16
+ handleLogout: () => void;
17
17
  hasDeveloperOnboarding?: boolean;
18
18
  }
19
19
 
@@ -24,10 +24,6 @@ export function UserProfile({
24
24
  }: UserProfileProps): JSX.Element {
25
25
  const [isOpened, setIsOpened] = useState<boolean>(false);
26
26
 
27
- const { userProfile: userProfileSettings } = useThemeConfig();
28
-
29
- const logoutRedirect = userProfileSettings?.logoutRedirect || '/';
30
-
31
27
  return (
32
28
  <StyledTooltip
33
29
  isOpen={isOpened}
@@ -36,16 +32,11 @@ export function UserProfile({
36
32
  placement="bottom"
37
33
  width="100%"
38
34
  tip={
39
- <StyledUl onClick={() => setIsOpened(false)}>
40
- {hasDeveloperOnboarding ? (
41
- <Link to="/apps">
42
- <StyledLi>My Apps</StyledLi>
43
- </Link>
44
- ) : null}
45
- <StyledLi onClick={() => handleLogout(logoutRedirect)}>
46
- {userProfileSettings?.logoutLabel || 'Log out'}
47
- </StyledLi>
48
- </StyledUl>
35
+ <UserProfileMenu
36
+ hasDeveloperOnboarding={hasDeveloperOnboarding}
37
+ handleLogout={handleLogout}
38
+ setIsOpened={setIsOpened}
39
+ />
49
40
  }
50
41
  >
51
42
  <Profile
@@ -62,33 +53,3 @@ const StyledTooltip = styled(Tooltip)`
62
53
  padding: 0;
63
54
  }
64
55
  `;
65
-
66
- const StyledUl = styled.ul`
67
- margin: 0;
68
- padding: 0;
69
- list-style: none;
70
- text-align: left;
71
- background-color: var(--search-modal-background);
72
- color: var(--search-modal-text-color);
73
- min-width: 100px;
74
- a {
75
- text-decoration: none;
76
- &:hover {
77
- color: inherit;
78
- }
79
- &:visited {
80
- color: inherit;
81
- }
82
- }
83
- `;
84
-
85
- const StyledLi = styled.li`
86
- cursor: pointer;
87
- font-size: 16px;
88
- list-style: none;
89
- padding: 15px 20px;
90
- transition: background-color 0.25s ease 0s;
91
- &:hover {
92
- background-color: rgba(0, 0, 0, 0.1);
93
- }
94
- `;
@@ -0,0 +1,83 @@
1
+ import React from 'react';
2
+ import styled, { css } from 'styled-components';
3
+
4
+ import { Link } from '@portal/Link';
5
+ import { useThemeConfig } from '@theme/hooks';
6
+
7
+ interface UserProfileMenuProps {
8
+ setIsOpened: (isOpen: boolean) => void;
9
+ handleLogout: () => void;
10
+ hasDeveloperOnboarding: boolean;
11
+ }
12
+
13
+ export function UserProfileMenu({
14
+ hasDeveloperOnboarding,
15
+ setIsOpened,
16
+ handleLogout,
17
+ }: UserProfileMenuProps): JSX.Element {
18
+ const { userProfile } = useThemeConfig();
19
+
20
+ return (
21
+ <>
22
+ <StyledUl onClick={() => setIsOpened(false)}>
23
+ {hasDeveloperOnboarding ? (
24
+ <Link to="/apps">
25
+ <StyledLi>My Apps</StyledLi>
26
+ </Link>
27
+ ) : null}
28
+
29
+ {userProfile?.menu?.map((item) => (
30
+ <Link external={item.external} key={item.label} to={item.link || ''}>
31
+ <StyledLi separatorLine={item?.separatorLine}>{item.label}</StyledLi>
32
+ </Link>
33
+ ))}
34
+
35
+ <StyledLi onClick={() => handleLogout()}>{userProfile?.logoutLabel || 'Log out'}</StyledLi>
36
+ </StyledUl>
37
+ </>
38
+ );
39
+ }
40
+
41
+ const StyledUl = styled.ul`
42
+ margin: var(--profile-menu-margin);
43
+ padding: var(--profile-menu-padding);
44
+ list-style: none;
45
+ background-color: var(--profile-menu-background-color);
46
+ min-width: 100px;
47
+ a {
48
+ text-decoration: none;
49
+ }
50
+ `;
51
+
52
+ const StyledLi = styled.li<{ separatorLine?: boolean }>`
53
+ cursor: pointer;
54
+ font-size: var(--profile-menu-item-font-size);
55
+ font-family: var(--profile-menu-item-font-family);
56
+ font-weight: var(--profile-menu-item-font-weight);
57
+ line-height: var(--profile-menu-item-line-height);
58
+ color: var(--profile-menu-item-text-color);
59
+ text-align: var(--profile-menu-item-text-align);
60
+
61
+ list-style: none;
62
+
63
+ padding: var(--profile-menu-item-padding-vertical) var(--profile-menu-item-padding-horizontal);
64
+
65
+ transition: background-color 0.25s ease 0s;
66
+
67
+ &:hover {
68
+ color: var(--profile-menu-item-hover-text-color);
69
+ background-color: var(--profile-menu-item-hover-background-color);
70
+ text-decoration: var(--profile-menu-item-hover-text-decoration);
71
+ }
72
+ &:active {
73
+ color: var(--profile-menu-item-active-text-color);
74
+ background-color: var(--profile-menu-item-active-background-color);
75
+ text-decoration: var(--profile-menu-item-active-text-decoration);
76
+ }
77
+
78
+ ${({ separatorLine }) =>
79
+ separatorLine &&
80
+ css`
81
+ border-bottom: 1px solid var(--profile-menu-item-separator-line-color);
82
+ `}
83
+ `;
package/src/config.ts CHANGED
@@ -215,7 +215,7 @@ export const ThemeConfig = z
215
215
  .object({
216
216
  loginLabel: z.string().default('Login').optional(),
217
217
  logoutLabel: z.string().default('Logout').optional(),
218
- logoutRedirect: z.string().default('/').optional(),
218
+ menu: z.array(z.any()).optional(), // should be same as navbar items type
219
219
  })
220
220
  .extend(HideConfig.shape)
221
221
  .optional()
@@ -1965,6 +1965,67 @@ const pages = css`
1965
1965
  // @tokens End
1966
1966
  `
1967
1967
 
1968
+ const userProfile = css`
1969
+
1970
+ /**
1971
+ * @tokens User Profile
1972
+ */
1973
+
1974
+ --profile-name-font-family: var(--navbar-item-font-family); // @presenter FontFamily
1975
+ --profile-name-font-size: var(--navbar-item-font-size); // @presenter FontSize
1976
+ --profile-name-font-weight: var(--font-weight-regular); // @presenter FontWeight
1977
+ --profile-name-line-height: var(--line-height-base); // @presenter LineHeight
1978
+ --profile-name-text-color: var(--navbar-text-color); // @presenter Color
1979
+
1980
+ --profile-name-padding-horizontal: 0; // @presenter Spacing
1981
+ --profile-name-padding-vertical: 0; // @presenter Spacing
1982
+ --profile-name-margin-horizontal: 0; // @presenter Spacing
1983
+
1984
+ --profile-avatar-width: 40px;
1985
+ --profile-avatar-height: 40px;
1986
+ --profile-avatar-border-radius: 50%; // @presenter BorderRadius
1987
+
1988
+ --profile-avatar-font-family: var(--navbar-item-font-family); // @presenter FontFamily
1989
+ --profile-avatar-font-size: var(--navbar-item-font-size) // @presenter FontSize
1990
+ --profile-avatar-font-weight: var(--font-weight-regular); // @presenter FontWeight
1991
+ --profile-avatar-line-height: var(--line-height-base); // @presenter LineHeight
1992
+
1993
+ --profile-avatar-margin-left: 16px; // @presenter Spacing
1994
+ --profile-avatar-padding-vertical: 16px; // @presenter Spacing
1995
+ --profile-avatar-padding-horizontal: 16px; // @presenter Spacing
1996
+
1997
+
1998
+ /**
1999
+ * @tokens User Profile Menu
2000
+ */
2001
+
2002
+ --profile-menu-background-color: var(--background-color); // @presenter Color
2003
+ --profile-menu-padding: 0px; // @presenter Spacing
2004
+ --profile-menu-margin: 0px; // @presenter Spacing
2005
+
2006
+ --profile-menu-item-font-family: var(--navbar-item-font-family); // @presenter FontFamily
2007
+ --profile-menu-item-text-color: var(--text-color); // @presenter Color
2008
+ --profile-menu-item-font-size: var(--navbar-item-font-size); // @presenter FontSize
2009
+ --profile-menu-item-font-weight: var(--font-weight-regular); // @presenter FontWeight
2010
+ --profile-menu-item-line-height: var(--line-height-base); // @presenter LineHeight
2011
+ --profile-menu-item-text-align: left;
2012
+
2013
+ --profile-menu-item-padding-horizontal: 15px; // @presenter Spacing
2014
+ --profile-menu-item-padding-vertical: 20px; // @presenter Spacing
2015
+
2016
+ --profile-menu-item-hover-background-color: var(--color-primary-300); // @presenter Color
2017
+ --profile-menu-item-active-background-color: var(--color-primary-300); // @presenter Color
2018
+ --profile-menu-item-hover-text-color: var(--navbar-text-color); // @presenter Color
2019
+ --profile-menu-item-hover-text-decoration: none;
2020
+ --profile-menu-item-active-text-color: var(--navbar-text-color); // @presenter Color
2021
+ --profile-menu-item-active-text-decoration: none;
2022
+
2023
+ --profile-menu-item-separator-line-color: var(--border-color); // @presenter Color
2024
+
2025
+ // @tokens End
2026
+
2027
+ `;
2028
+
1968
2029
  const modal = css`
1969
2030
  body:has(.modal) {
1970
2031
  overflow: hidden;
@@ -2005,6 +2066,7 @@ export const styles = css`
2005
2066
  ${loadProgressBar}
2006
2067
  ${pages}
2007
2068
  ${modal}
2069
+ ${userProfile}
2008
2070
 
2009
2071
  --api-onboarding-table-text-color: #4e5356;
2010
2072
  --api-catalog-card-min-width: 250px;
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useState, useMemo } from 'react';
2
2
  import throttle from 'lodash.throttle';
3
3
 
4
- import type { Location } from 'history';
4
+ import type { Location } from 'react-router-dom';
5
5
 
6
6
  import { useNavbarHeight } from '@theme/hooks/useNavbarHeight';
7
7
 
@@ -10,9 +10,12 @@ export type UseActiveSectionIdReturnType = string;
10
10
  export function useActiveSectionId(
11
11
  location: Location,
12
12
  hasOverviewPage = false,
13
+ withNavbar = true,
13
14
  ): UseActiveSectionIdReturnType {
14
15
  const [itemId, setItemId] = useState<string>('');
15
16
  const navbarHeight = useNavbarHeight(location);
17
+ const heightOffset = (withNavbar ? navbarHeight : 0) + 5; // use small padding 5px to account for rounding;
18
+
16
19
  const scrollListener = useMemo(
17
20
  () =>
18
21
  throttle(() => {
@@ -24,8 +27,7 @@ export function useActiveSectionId(
24
27
  for (let i = 0; i < sections.length; i++) {
25
28
  const section = sections[i];
26
29
  const rect = section.getBoundingClientRect();
27
- if (rect.y < navbarHeight + 5 && rect.bottom > navbarHeight + 5) {
28
- // use small padding 5px to account for rounding
30
+ if (rect.y < heightOffset && rect.bottom > heightOffset) {
29
31
  setItemId(section.getAttribute('data-section-id') || '');
30
32
  return;
31
33
  }
@@ -34,7 +36,7 @@ export function useActiveSectionId(
34
36
  setItemId('');
35
37
  }
36
38
  }, 150),
37
- [navbarHeight, hasOverviewPage],
39
+ [heightOffset, hasOverviewPage],
38
40
  );
39
41
 
40
42
  useEffect(() => {
@@ -46,7 +48,7 @@ export function useActiveSectionId(
46
48
  return () => {
47
49
  window.removeEventListener('scroll', scrollListener);
48
50
  };
49
- }, [location, navbarHeight, scrollListener]);
51
+ }, [location, heightOffset, scrollListener]);
50
52
 
51
53
  return itemId;
52
54
  }
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useState } from 'react';
2
2
 
3
- import type { Location } from 'history';
3
+ import type { Location } from 'react-router-dom';
4
4
 
5
5
  import { getNavbarElement } from '@theme/utils/getNavbarElement';
6
6