@redocly/theme 0.69.0-next.2 → 0.69.0-next.4

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.
@@ -5,5 +5,7 @@ export type BadgeProps = PropsWithChildren<{
5
5
  key?: string;
6
6
  className?: string;
7
7
  icon?: string;
8
+ /** Inline SVG content for the icon */
9
+ iconRawContent?: string;
8
10
  }>;
9
- export declare function Badge({ icon, children, deprecated, color, ...props }: BadgeProps): JSX.Element;
11
+ export declare function Badge({ icon, iconRawContent, children, deprecated, color, ...props }: BadgeProps): JSX.Element;
@@ -52,9 +52,9 @@ const react_1 = __importDefault(require("react"));
52
52
  const styled_components_1 = __importStar(require("styled-components"));
53
53
  const GenericIcon_1 = require("../../icons/GenericIcon/GenericIcon");
54
54
  function Badge(_a) {
55
- var { icon, children, deprecated, color } = _a, props = __rest(_a, ["icon", "children", "deprecated", "color"]);
55
+ var { icon, iconRawContent, children, deprecated, color } = _a, props = __rest(_a, ["icon", "iconRawContent", "children", "deprecated", "color"]);
56
56
  return (react_1.default.createElement(BadgeComponent, Object.assign({}, props, { $color: color, $deprecated: deprecated, "data-component-name": "Badge/Badge" }),
57
- icon ? react_1.default.createElement(BadgeIcon, { icon: icon }) : null,
57
+ icon || iconRawContent ? react_1.default.createElement(BadgeIcon, { icon: icon, rawContent: iconRawContent }) : null,
58
58
  children));
59
59
  }
60
60
  const BadgeComponent = styled_components_1.default.span `
@@ -82,5 +82,10 @@ const BadgeIcon = (0, styled_components_1.default)(GenericIcon_1.GenericIcon) `
82
82
  margin-right: var(--spacing-xxs);
83
83
  flex-shrink: 0;
84
84
  vertical-align: middle;
85
+
86
+ /* For inline SVG to align properly */
87
+ svg {
88
+ display: block;
89
+ }
85
90
  `;
86
91
  //# sourceMappingURL=Badge.js.map
@@ -5,16 +5,20 @@ const react_1 = require("react");
5
5
  const react_router_1 = require("react-router");
6
6
  const utils_1 = require("../utils");
7
7
  const constants_1 = require("../constants");
8
+ const TARGET_TOLERANCE = 2;
8
9
  function useActiveHeading(contentElement, displayedHeadings) {
9
10
  const [heading, setHeading] = (0, react_1.useState)(undefined);
10
11
  const [headingElements, setHeadingElements] = (0, react_1.useState)([]);
11
12
  const headingElementsRef = (0, react_1.useRef)({});
12
13
  const displayedHeadingsRef = (0, react_1.useRef)(displayedHeadings);
13
14
  const lockObserver = (0, react_1.useRef)(false);
15
+ const clickScroll = (0, react_1.useRef)(null);
14
16
  const location = (0, react_router_1.useLocation)();
15
17
  (0, react_1.useEffect)(() => {
16
18
  setHeading(undefined);
17
19
  headingElementsRef.current = {};
20
+ clickScroll.current = null;
21
+ lockObserver.current = false;
18
22
  }, [location.pathname]);
19
23
  (0, react_1.useEffect)(() => {
20
24
  displayedHeadingsRef.current = displayedHeadings;
@@ -98,13 +102,13 @@ function useActiveHeading(contentElement, displayedHeadings) {
98
102
  }, [calculateVirtualPositions]);
99
103
  const intersectionCallback = (0, react_1.useCallback)((entries) => {
100
104
  var _a, _b, _c;
101
- if (lockObserver.current) {
102
- return;
103
- }
104
105
  headingElementsRef.current = entries.reduce((map, entry) => {
105
106
  map[entry.target.id] = entry;
106
107
  return map;
107
108
  }, headingElementsRef.current);
109
+ if (lockObserver.current) {
110
+ return;
111
+ }
108
112
  if (isTopOfPage()) {
109
113
  const newHeading = ((_a = headingElements[0]) === null || _a === void 0 ? void 0 : _a.id) || undefined;
110
114
  setHeading(newHeading);
@@ -145,18 +149,56 @@ function useActiveHeading(contentElement, displayedHeadings) {
145
149
  }
146
150
  setHeading(visibleHeadings[0].target.id);
147
151
  }, [getIndexFromId, headingElements, getTriggerOffset, findActiveHeaderByVirtualPosition]);
152
+ const releaseClickScroll = (0, react_1.useCallback)(() => {
153
+ clickScroll.current = null;
154
+ lockObserver.current = false;
155
+ }, []);
156
+ const trackClickScroll = (0, react_1.useCallback)(() => {
157
+ const clickScrollState = clickScroll.current;
158
+ if (!clickScrollState) {
159
+ return;
160
+ }
161
+ const { top } = clickScrollState.target.getBoundingClientRect();
162
+ if (clickScrollState.reached) {
163
+ if (top >= 0 && top <= getTriggerOffset()) {
164
+ return;
165
+ }
166
+ }
167
+ else {
168
+ const distance = Math.abs(top - clickScrollState.scrollMargin);
169
+ if (distance <= TARGET_TOLERANCE) {
170
+ clickScrollState.reached = true;
171
+ return;
172
+ }
173
+ if (distance <= clickScrollState.closestDistance + TARGET_TOLERANCE) {
174
+ clickScrollState.closestDistance = Math.min(clickScrollState.closestDistance, distance);
175
+ return;
176
+ }
177
+ }
178
+ releaseClickScroll();
179
+ intersectionCallback([]);
180
+ }, [releaseClickScroll, intersectionCallback, getTriggerOffset]);
148
181
  const handleHeadingClick = (0, react_1.useCallback)((headingId) => {
149
- lockObserver.current = true;
182
+ releaseClickScroll();
150
183
  setHeading(headingId);
151
- const headingElement = headingElements.find((el) => el.id === headingId);
152
- if (headingElement && headingElement.scrollIntoView) {
153
- headingElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
154
- }
155
- setTimeout(() => {
156
- headingElementsRef.current = {};
157
- lockObserver.current = false;
158
- }, 300);
159
- }, [headingElements]);
184
+ const target = document.getElementById(headingId) || headingElements.find((el) => el.id === headingId);
185
+ if (!(target === null || target === void 0 ? void 0 : target.scrollIntoView)) {
186
+ return;
187
+ }
188
+ target.scrollIntoView({ behavior: 'smooth', block: 'start' });
189
+ if (!target.getClientRects().length) {
190
+ return;
191
+ }
192
+ const scrollMargin = parseFloat(getComputedStyle(target).scrollMarginTop) || 0;
193
+ const distance = Math.abs(target.getBoundingClientRect().top - scrollMargin);
194
+ lockObserver.current = true;
195
+ clickScroll.current = {
196
+ target,
197
+ scrollMargin,
198
+ closestDistance: distance,
199
+ reached: distance <= TARGET_TOLERANCE,
200
+ };
201
+ }, [headingElements, releaseClickScroll]);
160
202
  (0, react_1.useEffect)(() => {
161
203
  var _a;
162
204
  if (!contentElement) {
@@ -203,8 +245,10 @@ function useActiveHeading(contentElement, displayedHeadings) {
203
245
  });
204
246
  const handleScroll = () => {
205
247
  var _a;
206
- if (lockObserver.current)
248
+ if (clickScroll.current) {
249
+ trackClickScroll();
207
250
  return;
251
+ }
208
252
  if (isTopOfPage()) {
209
253
  setHeading(((_a = headingElements[0]) === null || _a === void 0 ? void 0 : _a.id) || undefined);
210
254
  }
@@ -214,7 +258,7 @@ function useActiveHeading(contentElement, displayedHeadings) {
214
258
  observer.disconnect();
215
259
  window.removeEventListener('scroll', handleScroll);
216
260
  };
217
- }, [headingElements, intersectionCallback, getTriggerOffset]);
261
+ }, [headingElements, intersectionCallback, getTriggerOffset, trackClickScroll]);
218
262
  return { heading, handleHeadingClick };
219
263
  }
220
264
  //# sourceMappingURL=use-active-heading.js.map
@@ -10,7 +10,14 @@ export type CardProps = React.PropsWithChildren<{
10
10
  layout?: 'horizontal' | 'vertical' | 'combined';
11
11
  variant?: 'filled' | 'outlined' | 'elevated' | 'ghost';
12
12
  iconVariant?: 'filled' | 'ghost';
13
+ iconColor?: string;
13
14
  align?: 'start' | 'center' | 'end';
14
15
  to?: string;
16
+ linkIcon?: 'chevron' | 'arrow';
17
+ cta?: React.ReactNode;
18
+ badge?: string;
19
+ badgeColor?: string;
20
+ badgeIcon?: string;
21
+ badgeIconRawContent?: string;
15
22
  }>;
16
- export declare function Card({ title, image, icon, iconRawContent, imagePosition, iconPosition, layout, variant, lineClamp, iconVariant, align, to, children, }: CardProps): React.JSX.Element;
23
+ export declare function Card({ title, image, icon, iconRawContent, imagePosition, iconPosition, layout, variant, lineClamp, iconVariant, iconColor, align, to, cta, linkIcon, badge, badgeColor, badgeIcon, badgeIconRawContent, children, }: CardProps): React.JSX.Element;
@@ -7,10 +7,12 @@ exports.Card = Card;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const styled_components_1 = __importDefault(require("styled-components"));
9
9
  const ChevronRightIcon_1 = require("../../../icons/ChevronRightIcon/ChevronRightIcon");
10
+ const ArrowUpRightIcon_1 = require("../../../icons/ArrowUpRightIcon/ArrowUpRightIcon");
10
11
  const CardImage_1 = require("../../../markdoc/components/Cards/CardImage");
11
12
  const CardIcon_1 = require("../../../markdoc/components/Cards/CardIcon");
13
+ const CardBadge_1 = require("../../../markdoc/components/Cards/CardBadge");
12
14
  const Link_1 = require("../../../components/Link/Link");
13
- function Card({ title = '', image, icon, iconRawContent, imagePosition = 'start', iconPosition = 'auto', layout = 'vertical', variant = 'filled', lineClamp, iconVariant = 'filled', align = 'start', to, children, }) {
15
+ function Card({ title = '', image, icon, iconRawContent, imagePosition = 'start', iconPosition = 'auto', layout = 'vertical', variant = 'filled', lineClamp, iconVariant = 'filled', iconColor, align = 'start', to, cta, linkIcon, badge, badgeColor, badgeIcon, badgeIconRawContent, children, }) {
14
16
  const titleNoSpaces = title === null || title === void 0 ? void 0 : title.replace(/\s+/g, '-').toLowerCase();
15
17
  const cardTitleId = `card-title-${titleNoSpaces}`;
16
18
  const justifyContent = align === 'center' ? 'center' : align === 'end' ? 'flex-end' : 'flex-start';
@@ -20,12 +22,20 @@ function Card({ title = '', image, icon, iconRawContent, imagePosition = 'start'
20
22
  react_1.default.createElement(react_1.default.Fragment, null,
21
23
  image ? react_1.default.createElement(CardImage_1.CardImage, { src: image, alt: title, position: imagePosition }) : null,
22
24
  react_1.default.createElement(CardContentWrapper, { $layout: layout, $alignItems: alignItems, $textAlign: textAlign, $hasImage: image !== undefined },
23
- icon && (react_1.default.createElement(CardIcon_1.CardIcon, { variant: iconVariant, src: icon, rawContent: iconRawContent, position: iconPosition })),
25
+ icon && (react_1.default.createElement(CardIcon_1.CardIcon, { variant: iconVariant, src: icon, rawContent: iconRawContent, position: iconPosition, color: iconColor })),
24
26
  react_1.default.createElement(ContentWrapper, null,
25
- react_1.default.createElement(Title, { id: cardTitleId, $isCardLink: !!to, $justifyContent: justifyContent },
26
- title,
27
- variant == 'ghost' && !!to && react_1.default.createElement(CardLinkIcon, null)),
28
- react_1.default.createElement(Body, { $lineClamp: lineClamp }, children))))));
27
+ react_1.default.createElement(TitleWrapper, { id: cardTitleId, $isCardLink: !!to, $justifyContent: justifyContent },
28
+ react_1.default.createElement(Title, null,
29
+ title,
30
+ !!to && !!linkIcon && (react_1.default.createElement(CardLinkIcon, { as: linkIcon === 'arrow' ? ArrowUpRightIcon_1.ArrowUpRightIcon : undefined }))),
31
+ !!badge && (react_1.default.createElement(CardBadge_1.CardBadge, { color: badgeColor, icon: badgeIcon, iconRawContent: badgeIconRawContent }, badge))),
32
+ react_1.default.createElement(Body, { $lineClamp: lineClamp }, children),
33
+ !!to && !!cta && (react_1.default.createElement(Cta, { $justifyContent: justifyContent },
34
+ react_1.default.createElement(CtaText, null,
35
+ " ",
36
+ cta,
37
+ " "),
38
+ react_1.default.createElement(CtaIcon, null))))))));
29
39
  }
30
40
  const CardContentWrapper = styled_components_1.default.div `
31
41
  display: flex;
@@ -44,24 +54,26 @@ const ContentWrapper = styled_components_1.default.div `
44
54
  gap: var(--card-content-gap);
45
55
  width: 100%;
46
56
  `;
47
- const Title = styled_components_1.default.h3 `
48
- margin: var(--card-title-margin);
49
- font-weight: var(--card-title-font-weight);
57
+ const TitleWrapper = styled_components_1.default.div `
50
58
  color: var(--card-title-text-color);
51
59
  display: flex;
52
60
  align-items: center;
61
+ flex-wrap: wrap;
62
+ row-gap: var(--card-title-row-gap);
53
63
  justify-content: ${({ $justifyContent }) => $justifyContent};
54
64
  width: 100%;
55
65
  `;
66
+ const Title = styled_components_1.default.h3 `
67
+ margin: var(--card-title-margin);
68
+ font-weight: var(--card-title-font-weight);
69
+ `;
70
+ // The `as` prop selects the icon; the chevron is the default target.
56
71
  const CardLinkIcon = (0, styled_components_1.default)(ChevronRightIcon_1.ChevronRightIcon) `
57
72
  width: var(--card-link-icon-width);
58
73
  height: var(--card-link-icon-height);
59
- color: var(--card-link-icon-color);
74
+ color: var(--icon-color-secondary);
60
75
  margin-left: var(--spacing-xxs);
61
- opacity: 0;
62
- transition:
63
- opacity 0.3s,
64
- transform 0.3s;
76
+ margin-top: calc(var(--spacing-xxs) / 2);
65
77
  `;
66
78
  const Body = styled_components_1.default.div `
67
79
  color: var(--card-text-color);
@@ -84,6 +96,26 @@ const Body = styled_components_1.default.div `
84
96
  margin-bottom: 0;
85
97
  }
86
98
  `;
99
+ const Cta = styled_components_1.default.div `
100
+ display: flex;
101
+ gap: var(--card-cta-gap);
102
+ align-items: center;
103
+ margin-top: var(--card-cta-margin-top);
104
+ color: var(--card-cta-text-color);
105
+ font-size: var(--card-cta-font-size);
106
+ justify-content: ${({ $justifyContent }) => $justifyContent};
107
+
108
+ && > * {
109
+ cursor: pointer;
110
+ }
111
+ `;
112
+ const CtaText = styled_components_1.default.span `
113
+ font-weight: var(--card-cta-font-weight);
114
+ `;
115
+ const CtaIcon = (0, styled_components_1.default)(ChevronRightIcon_1.ChevronRightIcon) `
116
+ width: var(--card-cta-icon-width);
117
+ height: var(--card-cta-icon-height);
118
+ `;
87
119
  const CardWrapper = styled_components_1.default.div.attrs(({ $isCardLink, $variant }) => ({
88
120
  className: getCardWrapperClass($isCardLink, $variant),
89
121
  })) `
@@ -112,6 +144,13 @@ const CardWrapper = styled_components_1.default.div.attrs(({ $isCardLink, $varia
112
144
  height: 100%;
113
145
  object-fit: cover;
114
146
  `}
147
+ }
148
+
149
+ &:hover,
150
+ &:focus-within {
151
+ ${Cta} {
152
+ color: var(--card-link-icon-color);
153
+ }
115
154
  }
116
155
 
117
156
  &.card-link {
@@ -130,8 +169,8 @@ const CardWrapper = styled_components_1.default.div.attrs(({ $isCardLink, $varia
130
169
  background-color: var(--card-icon-bg-color-hover);
131
170
  }
132
171
 
133
- svg {
134
- opacity: 1;
172
+ ${CardLinkIcon} {
173
+ color: var(--card-link-icon-color);
135
174
  }
136
175
  }
137
176
  }
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ export type CardBadgeProps = React.PropsWithChildren<{
3
+ color?: string;
4
+ icon?: string;
5
+ /** Inline SVG content for the icon */
6
+ iconRawContent?: string;
7
+ className?: string;
8
+ }>;
9
+ export declare function CardBadge({ color, icon, iconRawContent, className, children }: CardBadgeProps): React.JSX.Element;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CardBadge = CardBadge;
7
+ const react_1 = __importDefault(require("react"));
8
+ const styled_components_1 = __importDefault(require("styled-components"));
9
+ const Badge_1 = require("../../../components/Badge/Badge");
10
+ function CardBadge({ color, icon, iconRawContent, className, children }) {
11
+ // `tag-default` keeps a badge grey when the name is absent or unknown.
12
+ const classNames = ['tag-default', color && `tag-${color}`, className].filter(Boolean).join(' ');
13
+ return (react_1.default.createElement(CardBadgeWrapper, { className: classNames, icon: icon, iconRawContent: iconRawContent }, children));
14
+ }
15
+ const CardBadgeWrapper = (0, styled_components_1.default)(Badge_1.Badge) `
16
+ display: flex;
17
+ align-items: center;
18
+ margin: var(--card-badge-margin);
19
+ padding: var(--card-badge-padding);
20
+ border-radius: var(--card-badge-border-radius);
21
+ font-size: var(--card-badge-font-size);
22
+ font-weight: var(--card-badge-font-weight);
23
+ line-height: var(--card-badge-line-height);
24
+ white-space: nowrap;
25
+ flex-shrink: 0;
26
+
27
+ --badge-text-color: var(--tag-color);
28
+ --badge-bg-color: var(--tag-bg-color);
29
+ `;
30
+ //# sourceMappingURL=CardBadge.js.map
@@ -1,15 +1,16 @@
1
1
  import React from 'react';
2
2
  export type CardIconProps = {
3
- variant?: string;
3
+ variant?: 'filled' | 'ghost';
4
4
  src: string;
5
5
  rawContent?: string;
6
6
  position?: 'auto' | 'start' | 'center' | 'end';
7
+ color?: string;
7
8
  };
8
- export declare function CardIcon({ variant, src, rawContent, position }: CardIconProps): React.JSX.Element;
9
+ export declare function CardIcon({ variant, src, rawContent, position, color }: CardIconProps): React.JSX.Element;
9
10
  export declare const CardIconWrapper: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "$position" | "$variant"> & {
10
- $variant?: string;
11
+ $variant?: 'filled' | 'ghost';
11
12
  $position?: string;
12
13
  }, never> & Partial<Pick<import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "$position" | "$variant"> & {
13
- $variant?: string;
14
+ $variant?: 'filled' | 'ghost';
14
15
  $position?: string;
15
16
  }, never>>> & string;
@@ -8,14 +8,17 @@ exports.CardIcon = CardIcon;
8
8
  const react_1 = __importDefault(require("react"));
9
9
  const styled_components_1 = __importDefault(require("styled-components"));
10
10
  const GenericIcon_1 = require("../../../icons/GenericIcon/GenericIcon");
11
- function CardIcon({ variant, src, rawContent, position }) {
12
- return (react_1.default.createElement(exports.CardIconWrapper, { $variant: variant, $position: position },
11
+ function CardIcon({ variant, src, rawContent, position, color }) {
12
+ return (react_1.default.createElement(exports.CardIconWrapper, { className: color && `card-icon-color-${color}`, $variant: variant, $position: position },
13
13
  react_1.default.createElement(GenericIcon_1.GenericIcon, { icon: src, rawContent: rawContent })));
14
14
  }
15
15
  exports.CardIconWrapper = styled_components_1.default.div `
16
+ --card-icon-bg-color-hover: ${({ $variant }) => $variant === 'ghost' && 'transparent'};
16
17
  --icon-width: var(--card-icon-width);
17
18
  --icon-height: var(--card-icon-height);
18
19
  color: var(--card-icon-color);
20
+ /* An inline SVG fills from this. */
21
+ --icon-color: var(--card-icon-color);
19
22
 
20
23
  display: flex;
21
24
  align-self: ${({ $position }) => ($position ? $position : 'auto')};
@@ -24,7 +27,7 @@ exports.CardIconWrapper = styled_components_1.default.div `
24
27
  min-height: var(--card-icon-height);
25
28
  flex-shrink: 0;
26
29
 
27
- padding: var(--card-icon-padding);
30
+ padding: ${({ $variant }) => ($variant === 'ghost' ? 0 : `var(--card-icon-padding)`)};
28
31
  border: ${({ $variant }) => $variant !== 'ghost' && `var(--card-icon-border)`};
29
32
  border-radius: var(--card-icon-border-radius);
30
33
  overflow: hidden;
@@ -23,6 +23,38 @@ exports.cardsDarkMode = (0, styled_components_1.css) `
23
23
  --card-box-shadow: 0px 8px 24px -4px #00000014, 0px 6px 12px -6px #0000001F;
24
24
  --card-box-shadow-hover: 0px 14px 64px -4px #0000001F, 0px 8px 22px -6px #0000001F;
25
25
  }
26
+
27
+ .card-icon-color-green {
28
+ --card-icon-color: var(--color-green-7);
29
+ }
30
+
31
+ .card-icon-color-grey {
32
+ --card-icon-color: var(--color-warm-grey-8);
33
+ }
34
+
35
+ .card-icon-color-turquoise {
36
+ --card-icon-color: var(--color-turquoise-6);
37
+ }
38
+
39
+ .card-icon-color-magenta {
40
+ --card-icon-color: var(--color-magenta-4);
41
+ }
42
+
43
+ .card-icon-color-carrot {
44
+ --card-icon-color: var(--color-carrot-6);
45
+ }
46
+
47
+ .card-icon-color-grass {
48
+ --card-icon-color: var(--color-grass-7);
49
+ }
50
+
51
+ .card-icon-color-persian-green {
52
+ --card-icon-color: var(--color-persian-green-7);
53
+ }
54
+
55
+ .card-icon-color-sky {
56
+ --card-icon-color: var(--color-sky-6);
57
+ }
26
58
  `;
27
59
  /* eslint-enable theme/no-raw-colors-in-styles */
28
60
  //# sourceMappingURL=variables.dark.js.map
@@ -23,8 +23,8 @@ exports.cards = (0, styled_components_1.css) `
23
23
  --card-border-radius: var(--border-radius-xl);
24
24
  --card-border-style: solid;
25
25
  --card-link-cursor: pointer;
26
- --card-link-icon-width: var(--h3-font-size);
27
- --card-link-icon-height: var(--h3-font-size);
26
+ --card-link-icon-width: 14px;
27
+ --card-link-icon-height: 14px;
28
28
  --card-border-width: 1px;
29
29
  --card-image-border-radius: var(--border-radius-md);
30
30
  --card-image-aspect-ratio: 16 / 9;
@@ -35,12 +35,26 @@ exports.cards = (0, styled_components_1.css) `
35
35
  --card-icon-height: var(--card-icon-size);
36
36
  --card-icon-width: var(--card-icon-size);
37
37
  --card-icon-gap: var(--spacing-base);
38
- --card-title-margin: 0;
38
+ --card-title-margin: 0 var(--spacing-xs) 0 0;
39
39
  --card-title-font-weight: var(--font-weight-semibold);
40
+ --card-title-row-gap: var(--spacing-xxs);
40
41
  --card-content-gap: 4px;
41
42
  --card-padding: 16px 20px;
42
43
  --card-body-font-size: var(--font-size-base);
43
44
  --card-body-line-height: var(--line-height-base);
45
+ --card-cta-margin-top: var(--spacing-base);
46
+ --card-cta-gap: var(--spacing-xxs);
47
+ --card-cta-font-size: var(--font-size-base);
48
+ --card-cta-font-weight: var(--font-weight-regular);
49
+ --card-cta-text-color: var(--text-color-secondary);
50
+ --card-cta-icon-width: 14px;
51
+ --card-cta-icon-height: 14px;
52
+ --card-badge-margin: 0 var(--spacing-xxs) 0 0 ;
53
+ --card-badge-padding: 0 var(--spacing-xxs);
54
+ --card-badge-border-radius: var(--border-radius);
55
+ --card-badge-font-size: var(--font-size-sm);
56
+ --card-badge-font-weight: var(--font-weight-regular);
57
+ --card-badge-line-height: var(--line-height-sm);
44
58
 
45
59
  /**
46
60
  * @tokens Card variants
@@ -84,6 +98,64 @@ exports.cards = (0, styled_components_1.css) `
84
98
  --card-hover-transition: initial;
85
99
  }
86
100
 
101
+ /**
102
+ * @tokens Card icon colors
103
+ */
104
+ .card-icon-color-red {
105
+ --card-icon-color: var(--color-red-7);
106
+ }
107
+
108
+ .card-icon-color-green {
109
+ --card-icon-color: var(--color-green-8);
110
+ }
111
+
112
+ .card-icon-color-blue {
113
+ --card-icon-color: var(--color-blue-7);
114
+ }
115
+
116
+ .card-icon-color-grey {
117
+ --card-icon-color: var(--text-color-secondary);
118
+ }
119
+
120
+ .card-icon-color-turquoise {
121
+ --card-icon-color: var(--color-turquoise-8);
122
+ }
123
+
124
+ .card-icon-color-magenta {
125
+ --card-icon-color: var(--color-magenta-7);
126
+ }
127
+
128
+ .card-icon-color-purple {
129
+ --card-icon-color: var(--color-purple-7);
130
+ }
131
+
132
+ .card-icon-color-carrot {
133
+ --card-icon-color: var(--color-carrot-8);
134
+ }
135
+
136
+ .card-icon-color-raspberry {
137
+ --card-icon-color: var(--color-raspberry-7);
138
+ }
139
+
140
+ .card-icon-color-orange {
141
+ --card-icon-color: var(--color-orange-7);
142
+ }
143
+
144
+ .card-icon-color-grass {
145
+ --card-icon-color: var(--color-grass-8);
146
+ }
147
+
148
+ .card-icon-color-persian-green {
149
+ --card-icon-color: var(--color-persian-green-8);
150
+ }
151
+
152
+ .card-icon-color-sky {
153
+ --card-icon-color: var(--color-sky-8);
154
+ }
155
+
156
+ .card-icon-color-blueberry {
157
+ --card-icon-color: var(--color-blueberry-7);
158
+ }
87
159
  `;
88
- /* eslint-enable theme/no-raw-colors-in-styles */
160
+ /* eslint-enable theme/no-raw-colors-in-styles */
89
161
  //# sourceMappingURL=variables.js.map
@@ -52,7 +52,21 @@ exports.card = {
52
52
  type: String,
53
53
  matches: ['filled', 'ghost'],
54
54
  },
55
+ iconColor: { type: String },
55
56
  to: { type: String, resolver: 'link' },
57
+ linkIcon: {
58
+ type: String,
59
+ matches: ['arrow', 'chevron'],
60
+ },
61
+ cta: { type: String },
62
+ badge: { type: String },
63
+ badgeColor: { type: String },
64
+ badgeIcon: { type: String, resolver: 'relativeOrCdnIcon' },
65
+ badgeIconRawContent: {
66
+ // internal use only
67
+ type: String,
68
+ render: true,
69
+ },
56
70
  },
57
71
  },
58
72
  tagName: 'card',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.69.0-next.2",
3
+ "version": "0.69.0-next.4",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -68,12 +68,12 @@
68
68
  "vitest": "4.1.10",
69
69
  "vitest-when": "0.6.2",
70
70
  "webpack": "5.105.2",
71
- "@redocly/realm-asyncapi-sdk": "0.15.0-next.1"
71
+ "@redocly/realm-asyncapi-sdk": "0.15.0-next.2"
72
72
  },
73
73
  "dependencies": {
74
74
  "@tanstack/react-query": "5.62.3",
75
75
  "@tanstack/react-virtual": "3.13.0",
76
- "@xyflow/react": "^12.8.2",
76
+ "@xyflow/react": "12.8.3",
77
77
  "copy-to-clipboard": "3.3.3",
78
78
  "file-saver": "2.0.5",
79
79
  "highlight-words-core": "1.2.2",
@@ -11,9 +11,18 @@ export type BadgeProps = PropsWithChildren<{
11
11
  key?: string;
12
12
  className?: string;
13
13
  icon?: string;
14
+ /** Inline SVG content for the icon */
15
+ iconRawContent?: string;
14
16
  }>;
15
17
 
16
- export function Badge({ icon, children, deprecated, color, ...props }: BadgeProps): JSX.Element {
18
+ export function Badge({
19
+ icon,
20
+ iconRawContent,
21
+ children,
22
+ deprecated,
23
+ color,
24
+ ...props
25
+ }: BadgeProps): JSX.Element {
17
26
  return (
18
27
  <BadgeComponent
19
28
  {...props}
@@ -21,7 +30,7 @@ export function Badge({ icon, children, deprecated, color, ...props }: BadgeProp
21
30
  $deprecated={deprecated}
22
31
  data-component-name="Badge/Badge"
23
32
  >
24
- {icon ? <BadgeIcon icon={icon} /> : null}
33
+ {icon || iconRawContent ? <BadgeIcon icon={icon} rawContent={iconRawContent} /> : null}
25
34
  {children}
26
35
  </BadgeComponent>
27
36
  );
@@ -54,4 +63,9 @@ const BadgeIcon = styled(GenericIcon)`
54
63
  margin-right: var(--spacing-xxs);
55
64
  flex-shrink: 0;
56
65
  vertical-align: middle;
66
+
67
+ /* For inline SVG to align properly */
68
+ svg {
69
+ display: block;
70
+ }
57
71
  `;
@@ -8,6 +8,15 @@ type HeadingEntry = {
8
8
  [key: string]: IntersectionObserverEntry;
9
9
  };
10
10
 
11
+ type ClickScroll = {
12
+ target: HTMLElement;
13
+ scrollMargin: number;
14
+ closestDistance: number;
15
+ reached: boolean;
16
+ };
17
+
18
+ const TARGET_TOLERANCE = 2;
19
+
11
20
  export function useActiveHeading(
12
21
  contentElement: HTMLDivElement | null,
13
22
  displayedHeadings?: Array<{ id: string; depth: number } | null>,
@@ -17,11 +26,14 @@ export function useActiveHeading(
17
26
  const headingElementsRef = useRef<HeadingEntry>({});
18
27
  const displayedHeadingsRef = useRef(displayedHeadings);
19
28
  const lockObserver = useRef<boolean>(false);
29
+ const clickScroll = useRef<ClickScroll | null>(null);
20
30
  const location = useLocation();
21
31
 
22
32
  useEffect(() => {
23
33
  setHeading(undefined);
24
34
  headingElementsRef.current = {};
35
+ clickScroll.current = null;
36
+ lockObserver.current = false;
25
37
  }, [location.pathname]);
26
38
 
27
39
  useEffect(() => {
@@ -133,10 +145,6 @@ export function useActiveHeading(
133
145
 
134
146
  const intersectionCallback = useCallback(
135
147
  (entries: IntersectionObserverEntry[]) => {
136
- if (lockObserver.current) {
137
- return;
138
- }
139
-
140
148
  headingElementsRef.current = entries.reduce(
141
149
  (map: HeadingEntry, entry: IntersectionObserverEntry) => {
142
150
  map[entry.target.id] = entry;
@@ -145,6 +153,10 @@ export function useActiveHeading(
145
153
  headingElementsRef.current,
146
154
  );
147
155
 
156
+ if (lockObserver.current) {
157
+ return;
158
+ }
159
+
148
160
  if (isTopOfPage()) {
149
161
  const newHeading = headingElements[0]?.id || undefined;
150
162
  setHeading(newHeading);
@@ -202,22 +214,71 @@ export function useActiveHeading(
202
214
  [getIndexFromId, headingElements, getTriggerOffset, findActiveHeaderByVirtualPosition],
203
215
  );
204
216
 
217
+ const releaseClickScroll = useCallback(() => {
218
+ clickScroll.current = null;
219
+ lockObserver.current = false;
220
+ }, []);
221
+
222
+ const trackClickScroll = useCallback(() => {
223
+ const clickScrollState = clickScroll.current;
224
+ if (!clickScrollState) {
225
+ return;
226
+ }
227
+
228
+ const { top } = clickScrollState.target.getBoundingClientRect();
229
+
230
+ if (clickScrollState.reached) {
231
+ if (top >= 0 && top <= getTriggerOffset()) {
232
+ return;
233
+ }
234
+ } else {
235
+ const distance = Math.abs(top - clickScrollState.scrollMargin);
236
+
237
+ if (distance <= TARGET_TOLERANCE) {
238
+ clickScrollState.reached = true;
239
+ return;
240
+ }
241
+
242
+ if (distance <= clickScrollState.closestDistance + TARGET_TOLERANCE) {
243
+ clickScrollState.closestDistance = Math.min(clickScrollState.closestDistance, distance);
244
+ return;
245
+ }
246
+ }
247
+
248
+ releaseClickScroll();
249
+ intersectionCallback([]);
250
+ }, [releaseClickScroll, intersectionCallback, getTriggerOffset]);
251
+
205
252
  const handleHeadingClick = useCallback(
206
253
  (headingId: string) => {
207
- lockObserver.current = true;
254
+ releaseClickScroll();
208
255
  setHeading(headingId);
209
256
 
210
- const headingElement = headingElements.find((el) => el.id === headingId);
211
- if (headingElement && headingElement.scrollIntoView) {
212
- headingElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
257
+ const target =
258
+ document.getElementById(headingId) || headingElements.find((el) => el.id === headingId);
259
+
260
+ if (!target?.scrollIntoView) {
261
+ return;
262
+ }
263
+
264
+ target.scrollIntoView({ behavior: 'smooth', block: 'start' });
265
+
266
+ if (!target.getClientRects().length) {
267
+ return;
213
268
  }
214
269
 
215
- setTimeout(() => {
216
- headingElementsRef.current = {};
217
- lockObserver.current = false;
218
- }, 300);
270
+ const scrollMargin = parseFloat(getComputedStyle(target).scrollMarginTop) || 0;
271
+ const distance = Math.abs(target.getBoundingClientRect().top - scrollMargin);
272
+
273
+ lockObserver.current = true;
274
+ clickScroll.current = {
275
+ target,
276
+ scrollMargin,
277
+ closestDistance: distance,
278
+ reached: distance <= TARGET_TOLERANCE,
279
+ };
219
280
  },
220
- [headingElements],
281
+ [headingElements, releaseClickScroll],
221
282
  );
222
283
 
223
284
  useEffect(() => {
@@ -268,7 +329,10 @@ export function useActiveHeading(
268
329
  });
269
330
 
270
331
  const handleScroll = () => {
271
- if (lockObserver.current) return;
332
+ if (clickScroll.current) {
333
+ trackClickScroll();
334
+ return;
335
+ }
272
336
 
273
337
  if (isTopOfPage()) {
274
338
  setHeading(headingElements[0]?.id || undefined);
@@ -281,7 +345,7 @@ export function useActiveHeading(
281
345
  observer.disconnect();
282
346
  window.removeEventListener('scroll', handleScroll);
283
347
  };
284
- }, [headingElements, intersectionCallback, getTriggerOffset]);
348
+ }, [headingElements, intersectionCallback, getTriggerOffset, trackClickScroll]);
285
349
 
286
350
  return { heading, handleHeadingClick };
287
351
  }
@@ -2,8 +2,10 @@ import React from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import { ChevronRightIcon } from '@redocly/theme/icons/ChevronRightIcon/ChevronRightIcon';
5
+ import { ArrowUpRightIcon } from '@redocly/theme/icons/ArrowUpRightIcon/ArrowUpRightIcon';
5
6
  import { CardImage } from '@redocly/theme/markdoc/components/Cards/CardImage';
6
7
  import { CardIcon, CardIconWrapper } from '@redocly/theme/markdoc/components/Cards/CardIcon';
8
+ import { CardBadge } from '@redocly/theme/markdoc/components/Cards/CardBadge';
7
9
  import { Link } from '@redocly/theme/components/Link/Link';
8
10
 
9
11
  export type CardProps = React.PropsWithChildren<{
@@ -17,8 +19,15 @@ export type CardProps = React.PropsWithChildren<{
17
19
  layout?: 'horizontal' | 'vertical' | 'combined';
18
20
  variant?: 'filled' | 'outlined' | 'elevated' | 'ghost';
19
21
  iconVariant?: 'filled' | 'ghost';
22
+ iconColor?: string;
20
23
  align?: 'start' | 'center' | 'end';
21
24
  to?: string;
25
+ linkIcon?: 'chevron' | 'arrow';
26
+ cta?: React.ReactNode;
27
+ badge?: string;
28
+ badgeColor?: string;
29
+ badgeIcon?: string;
30
+ badgeIconRawContent?: string;
22
31
  }>;
23
32
 
24
33
  export function Card({
@@ -32,8 +41,15 @@ export function Card({
32
41
  variant = 'filled',
33
42
  lineClamp,
34
43
  iconVariant = 'filled',
44
+ iconColor,
35
45
  align = 'start',
36
46
  to,
47
+ cta,
48
+ linkIcon,
49
+ badge,
50
+ badgeColor,
51
+ badgeIcon,
52
+ badgeIconRawContent,
37
53
  children,
38
54
  }: CardProps) {
39
55
  const titleNoSpaces = title?.replace(/\s+/g, '-').toLowerCase();
@@ -66,14 +82,30 @@ export function Card({
66
82
  src={icon}
67
83
  rawContent={iconRawContent}
68
84
  position={iconPosition}
85
+ color={iconColor}
69
86
  />
70
87
  )}
71
88
  <ContentWrapper>
72
- <Title id={cardTitleId} $isCardLink={!!to} $justifyContent={justifyContent}>
73
- {title}
74
- {variant == 'ghost' && !!to && <CardLinkIcon />}
75
- </Title>
89
+ <TitleWrapper id={cardTitleId} $isCardLink={!!to} $justifyContent={justifyContent}>
90
+ <Title>
91
+ {title}
92
+ {!!to && !!linkIcon && (
93
+ <CardLinkIcon as={linkIcon === 'arrow' ? ArrowUpRightIcon : undefined} />
94
+ )}
95
+ </Title>
96
+ {!!badge && (
97
+ <CardBadge color={badgeColor} icon={badgeIcon} iconRawContent={badgeIconRawContent}>
98
+ {badge}
99
+ </CardBadge>
100
+ )}
101
+ </TitleWrapper>
76
102
  <Body $lineClamp={lineClamp}>{children}</Body>
103
+ {!!to && !!cta && (
104
+ <Cta $justifyContent={justifyContent}>
105
+ <CtaText> {cta} </CtaText>
106
+ <CtaIcon />
107
+ </Cta>
108
+ )}
77
109
  </ContentWrapper>
78
110
  </CardContentWrapper>
79
111
  </>
@@ -105,25 +137,28 @@ const ContentWrapper = styled.div`
105
137
  width: 100%;
106
138
  `;
107
139
 
108
- const Title = styled.h3<{ $isCardLink: boolean; $justifyContent: string }>`
109
- margin: var(--card-title-margin);
110
- font-weight: var(--card-title-font-weight);
140
+ const TitleWrapper = styled.div<{ $isCardLink: boolean; $justifyContent: string }>`
111
141
  color: var(--card-title-text-color);
112
142
  display: flex;
113
143
  align-items: center;
144
+ flex-wrap: wrap;
145
+ row-gap: var(--card-title-row-gap);
114
146
  justify-content: ${({ $justifyContent }) => $justifyContent};
115
147
  width: 100%;
116
148
  `;
117
149
 
150
+ const Title = styled.h3`
151
+ margin: var(--card-title-margin);
152
+ font-weight: var(--card-title-font-weight);
153
+ `;
154
+
155
+ // The `as` prop selects the icon; the chevron is the default target.
118
156
  const CardLinkIcon = styled(ChevronRightIcon)`
119
157
  width: var(--card-link-icon-width);
120
158
  height: var(--card-link-icon-height);
121
- color: var(--card-link-icon-color);
159
+ color: var(--icon-color-secondary);
122
160
  margin-left: var(--spacing-xxs);
123
- opacity: 0;
124
- transition:
125
- opacity 0.3s,
126
- transform 0.3s;
161
+ margin-top: calc(var(--spacing-xxs) / 2);
127
162
  `;
128
163
 
129
164
  const Body = styled.div<{ $lineClamp?: number }>`
@@ -149,6 +184,29 @@ const Body = styled.div<{ $lineClamp?: number }>`
149
184
  }
150
185
  `;
151
186
 
187
+ const Cta = styled.div<{ $justifyContent: string }>`
188
+ display: flex;
189
+ gap: var(--card-cta-gap);
190
+ align-items: center;
191
+ margin-top: var(--card-cta-margin-top);
192
+ color: var(--card-cta-text-color);
193
+ font-size: var(--card-cta-font-size);
194
+ justify-content: ${({ $justifyContent }) => $justifyContent};
195
+
196
+ && > * {
197
+ cursor: pointer;
198
+ }
199
+ `;
200
+
201
+ const CtaText = styled.span`
202
+ font-weight: var(--card-cta-font-weight);
203
+ `;
204
+
205
+ const CtaIcon = styled(ChevronRightIcon)`
206
+ width: var(--card-cta-icon-width);
207
+ height: var(--card-cta-icon-height);
208
+ `;
209
+
152
210
  const CardWrapper = styled.div.attrs<{
153
211
  $variant: string;
154
212
  $isCardLink: boolean;
@@ -185,6 +243,13 @@ const CardWrapper = styled.div.attrs<{
185
243
  height: 100%;
186
244
  object-fit: cover;
187
245
  `}
246
+ }
247
+
248
+ &:hover,
249
+ &:focus-within {
250
+ ${Cta} {
251
+ color: var(--card-link-icon-color);
252
+ }
188
253
  }
189
254
 
190
255
  &.card-link {
@@ -203,8 +268,8 @@ const CardWrapper = styled.div.attrs<{
203
268
  background-color: var(--card-icon-bg-color-hover);
204
269
  }
205
270
 
206
- svg {
207
- opacity: 1;
271
+ ${CardLinkIcon} {
272
+ color: var(--card-link-icon-color);
208
273
  }
209
274
  }
210
275
  }
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import { Badge } from '@redocly/theme/components/Badge/Badge';
5
+
6
+ export type CardBadgeProps = React.PropsWithChildren<{
7
+ color?: string;
8
+ icon?: string;
9
+ /** Inline SVG content for the icon */
10
+ iconRawContent?: string;
11
+ className?: string;
12
+ }>;
13
+
14
+ export function CardBadge({ color, icon, iconRawContent, className, children }: CardBadgeProps) {
15
+ // `tag-default` keeps a badge grey when the name is absent or unknown.
16
+ const classNames = ['tag-default', color && `tag-${color}`, className].filter(Boolean).join(' ');
17
+
18
+ return (
19
+ <CardBadgeWrapper className={classNames} icon={icon} iconRawContent={iconRawContent}>
20
+ {children}
21
+ </CardBadgeWrapper>
22
+ );
23
+ }
24
+
25
+ const CardBadgeWrapper = styled(Badge)`
26
+ display: flex;
27
+ align-items: center;
28
+ margin: var(--card-badge-margin);
29
+ padding: var(--card-badge-padding);
30
+ border-radius: var(--card-badge-border-radius);
31
+ font-size: var(--card-badge-font-size);
32
+ font-weight: var(--card-badge-font-weight);
33
+ line-height: var(--card-badge-line-height);
34
+ white-space: nowrap;
35
+ flex-shrink: 0;
36
+
37
+ --badge-text-color: var(--tag-color);
38
+ --badge-bg-color: var(--tag-bg-color);
39
+ `;
@@ -4,24 +4,35 @@ import styled from 'styled-components';
4
4
  import { GenericIcon } from '@redocly/theme/icons/GenericIcon/GenericIcon';
5
5
 
6
6
  export type CardIconProps = {
7
- variant?: string;
7
+ variant?: 'filled' | 'ghost';
8
8
  src: string;
9
9
  rawContent?: string;
10
10
  position?: 'auto' | 'start' | 'center' | 'end';
11
+ color?: string;
11
12
  };
12
13
 
13
- export function CardIcon({ variant, src, rawContent, position }: CardIconProps) {
14
+ export function CardIcon({ variant, src, rawContent, position, color }: CardIconProps) {
14
15
  return (
15
- <CardIconWrapper $variant={variant} $position={position}>
16
+ <CardIconWrapper
17
+ className={color && `card-icon-color-${color}`}
18
+ $variant={variant}
19
+ $position={position}
20
+ >
16
21
  <GenericIcon icon={src} rawContent={rawContent} />
17
22
  </CardIconWrapper>
18
23
  );
19
24
  }
20
25
 
21
- export const CardIconWrapper = styled.div<{ $variant?: string; $position?: string }>`
26
+ export const CardIconWrapper = styled.div<{
27
+ $variant?: 'filled' | 'ghost';
28
+ $position?: string;
29
+ }>`
30
+ --card-icon-bg-color-hover: ${({ $variant }) => $variant === 'ghost' && 'transparent'};
22
31
  --icon-width: var(--card-icon-width);
23
32
  --icon-height: var(--card-icon-height);
24
33
  color: var(--card-icon-color);
34
+ /* An inline SVG fills from this. */
35
+ --icon-color: var(--card-icon-color);
25
36
 
26
37
  display: flex;
27
38
  align-self: ${({ $position }) => ($position ? $position : 'auto')};
@@ -30,7 +41,7 @@ export const CardIconWrapper = styled.div<{ $variant?: string; $position?: strin
30
41
  min-height: var(--card-icon-height);
31
42
  flex-shrink: 0;
32
43
 
33
- padding: var(--card-icon-padding);
44
+ padding: ${({ $variant }) => ($variant === 'ghost' ? 0 : `var(--card-icon-padding)`)};
34
45
  border: ${({ $variant }) => $variant !== 'ghost' && `var(--card-icon-border)`};
35
46
  border-radius: var(--card-icon-border-radius);
36
47
  overflow: hidden;
@@ -21,5 +21,37 @@ export const cardsDarkMode = css`
21
21
  --card-box-shadow: 0px 8px 24px -4px #00000014, 0px 6px 12px -6px #0000001F;
22
22
  --card-box-shadow-hover: 0px 14px 64px -4px #0000001F, 0px 8px 22px -6px #0000001F;
23
23
  }
24
+
25
+ .card-icon-color-green {
26
+ --card-icon-color: var(--color-green-7);
27
+ }
28
+
29
+ .card-icon-color-grey {
30
+ --card-icon-color: var(--color-warm-grey-8);
31
+ }
32
+
33
+ .card-icon-color-turquoise {
34
+ --card-icon-color: var(--color-turquoise-6);
35
+ }
36
+
37
+ .card-icon-color-magenta {
38
+ --card-icon-color: var(--color-magenta-4);
39
+ }
40
+
41
+ .card-icon-color-carrot {
42
+ --card-icon-color: var(--color-carrot-6);
43
+ }
44
+
45
+ .card-icon-color-grass {
46
+ --card-icon-color: var(--color-grass-7);
47
+ }
48
+
49
+ .card-icon-color-persian-green {
50
+ --card-icon-color: var(--color-persian-green-7);
51
+ }
52
+
53
+ .card-icon-color-sky {
54
+ --card-icon-color: var(--color-sky-6);
55
+ }
24
56
  `;
25
57
  /* eslint-enable theme/no-raw-colors-in-styles */
@@ -21,8 +21,8 @@ export const cards = css`
21
21
  --card-border-radius: var(--border-radius-xl);
22
22
  --card-border-style: solid;
23
23
  --card-link-cursor: pointer;
24
- --card-link-icon-width: var(--h3-font-size);
25
- --card-link-icon-height: var(--h3-font-size);
24
+ --card-link-icon-width: 14px;
25
+ --card-link-icon-height: 14px;
26
26
  --card-border-width: 1px;
27
27
  --card-image-border-radius: var(--border-radius-md);
28
28
  --card-image-aspect-ratio: 16 / 9;
@@ -33,12 +33,26 @@ export const cards = css`
33
33
  --card-icon-height: var(--card-icon-size);
34
34
  --card-icon-width: var(--card-icon-size);
35
35
  --card-icon-gap: var(--spacing-base);
36
- --card-title-margin: 0;
36
+ --card-title-margin: 0 var(--spacing-xs) 0 0;
37
37
  --card-title-font-weight: var(--font-weight-semibold);
38
+ --card-title-row-gap: var(--spacing-xxs);
38
39
  --card-content-gap: 4px;
39
40
  --card-padding: 16px 20px;
40
41
  --card-body-font-size: var(--font-size-base);
41
42
  --card-body-line-height: var(--line-height-base);
43
+ --card-cta-margin-top: var(--spacing-base);
44
+ --card-cta-gap: var(--spacing-xxs);
45
+ --card-cta-font-size: var(--font-size-base);
46
+ --card-cta-font-weight: var(--font-weight-regular);
47
+ --card-cta-text-color: var(--text-color-secondary);
48
+ --card-cta-icon-width: 14px;
49
+ --card-cta-icon-height: 14px;
50
+ --card-badge-margin: 0 var(--spacing-xxs) 0 0 ;
51
+ --card-badge-padding: 0 var(--spacing-xxs);
52
+ --card-badge-border-radius: var(--border-radius);
53
+ --card-badge-font-size: var(--font-size-sm);
54
+ --card-badge-font-weight: var(--font-weight-regular);
55
+ --card-badge-line-height: var(--line-height-sm);
42
56
 
43
57
  /**
44
58
  * @tokens Card variants
@@ -82,5 +96,63 @@ export const cards = css`
82
96
  --card-hover-transition: initial;
83
97
  }
84
98
 
99
+ /**
100
+ * @tokens Card icon colors
101
+ */
102
+ .card-icon-color-red {
103
+ --card-icon-color: var(--color-red-7);
104
+ }
105
+
106
+ .card-icon-color-green {
107
+ --card-icon-color: var(--color-green-8);
108
+ }
109
+
110
+ .card-icon-color-blue {
111
+ --card-icon-color: var(--color-blue-7);
112
+ }
113
+
114
+ .card-icon-color-grey {
115
+ --card-icon-color: var(--text-color-secondary);
116
+ }
117
+
118
+ .card-icon-color-turquoise {
119
+ --card-icon-color: var(--color-turquoise-8);
120
+ }
121
+
122
+ .card-icon-color-magenta {
123
+ --card-icon-color: var(--color-magenta-7);
124
+ }
125
+
126
+ .card-icon-color-purple {
127
+ --card-icon-color: var(--color-purple-7);
128
+ }
129
+
130
+ .card-icon-color-carrot {
131
+ --card-icon-color: var(--color-carrot-8);
132
+ }
133
+
134
+ .card-icon-color-raspberry {
135
+ --card-icon-color: var(--color-raspberry-7);
136
+ }
137
+
138
+ .card-icon-color-orange {
139
+ --card-icon-color: var(--color-orange-7);
140
+ }
141
+
142
+ .card-icon-color-grass {
143
+ --card-icon-color: var(--color-grass-8);
144
+ }
145
+
146
+ .card-icon-color-persian-green {
147
+ --card-icon-color: var(--color-persian-green-8);
148
+ }
149
+
150
+ .card-icon-color-sky {
151
+ --card-icon-color: var(--color-sky-8);
152
+ }
153
+
154
+ .card-icon-color-blueberry {
155
+ --card-icon-color: var(--color-blueberry-7);
156
+ }
85
157
  `;
86
- /* eslint-enable theme/no-raw-colors-in-styles */
158
+ /* eslint-enable theme/no-raw-colors-in-styles */
@@ -52,7 +52,21 @@ export const card: MarkdocSchemaWrapper = {
52
52
  type: String,
53
53
  matches: ['filled', 'ghost'],
54
54
  },
55
+ iconColor: { type: String },
55
56
  to: { type: String, resolver: 'link' },
57
+ linkIcon: {
58
+ type: String,
59
+ matches: ['arrow', 'chevron'],
60
+ },
61
+ cta: { type: String },
62
+ badge: { type: String },
63
+ badgeColor: { type: String },
64
+ badgeIcon: { type: String, resolver: 'relativeOrCdnIcon' },
65
+ badgeIconRawContent: {
66
+ // internal use only
67
+ type: String,
68
+ render: true,
69
+ },
56
70
  },
57
71
  },
58
72
  tagName: 'card',