@redocly/theme 0.69.0-next.2 → 0.69.0-next.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/lib/components/Badge/Badge.d.ts +3 -1
- package/lib/components/Badge/Badge.js +7 -2
- package/lib/core/hooks/use-active-heading.js +59 -15
- package/lib/markdoc/components/Cards/Card.d.ts +8 -1
- package/lib/markdoc/components/Cards/Card.js +55 -16
- package/lib/markdoc/components/Cards/CardBadge.d.ts +9 -0
- package/lib/markdoc/components/Cards/CardBadge.js +30 -0
- package/lib/markdoc/components/Cards/CardIcon.d.ts +5 -4
- package/lib/markdoc/components/Cards/CardIcon.js +6 -3
- package/lib/markdoc/components/Cards/variables.dark.js +32 -0
- package/lib/markdoc/components/Cards/variables.js +76 -4
- package/lib/markdoc/tags/card.js +14 -0
- package/package.json +2 -2
- package/src/components/Badge/Badge.tsx +16 -2
- package/src/core/hooks/use-active-heading.ts +79 -15
- package/src/markdoc/components/Cards/Card.tsx +79 -14
- package/src/markdoc/components/Cards/CardBadge.tsx +39 -0
- package/src/markdoc/components/Cards/CardIcon.tsx +16 -5
- package/src/markdoc/components/Cards/variables.dark.ts +32 -0
- package/src/markdoc/components/Cards/variables.ts +76 -4
- package/src/markdoc/tags/card.ts +14 -0
|
@@ -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
|
-
|
|
182
|
+
releaseClickScroll();
|
|
150
183
|
setHeading(headingId);
|
|
151
|
-
const
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
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 (
|
|
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(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
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(--
|
|
74
|
+
color: var(--icon-color-secondary);
|
|
60
75
|
margin-left: var(--spacing-xxs);
|
|
61
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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:
|
|
27
|
-
--card-link-icon-height:
|
|
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
|
package/lib/markdoc/tags/card.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.69.0-next.3",
|
|
4
4
|
"description": "Shared UI components lib",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"theme",
|
|
@@ -68,7 +68,7 @@
|
|
|
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.
|
|
71
|
+
"@redocly/realm-asyncapi-sdk": "0.15.0-next.2"
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
74
|
"@tanstack/react-query": "5.62.3",
|
|
@@ -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({
|
|
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
|
-
|
|
254
|
+
releaseClickScroll();
|
|
208
255
|
setHeading(headingId);
|
|
209
256
|
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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 (
|
|
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
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
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(--
|
|
159
|
+
color: var(--icon-color-secondary);
|
|
122
160
|
margin-left: var(--spacing-xxs);
|
|
123
|
-
|
|
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
|
-
|
|
207
|
-
|
|
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?:
|
|
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
|
|
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<{
|
|
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:
|
|
25
|
-
--card-link-icon-height:
|
|
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 */
|
package/src/markdoc/tags/card.ts
CHANGED
|
@@ -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',
|