@orangesk/orange-design-system 3.2.0 → 3.3.0
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/build/components/Avatar/style.css +2 -0
- package/build/components/Avatar/style.css.map +1 -0
- package/build/components/Carousel/style.css +1 -1
- package/build/components/Carousel/style.css.map +1 -1
- package/build/components/Countdown/style.css +2 -0
- package/build/components/Countdown/style.css.map +1 -0
- package/build/components/DocumentationSidebar/style.css +1 -1
- package/build/components/DocumentationSidebar/style.css.map +1 -1
- package/build/components/Footer/style.css +1 -1
- package/build/components/Footer/style.css.map +1 -1
- package/build/components/Megamenu/style.css +1 -1
- package/build/components/Megamenu/style.css.map +1 -1
- package/build/components/Pill/style.css +1 -1
- package/build/components/Pill/style.css.map +1 -1
- package/build/components/Preview/style.css +1 -1
- package/build/components/Preview/style.css.map +1 -1
- package/build/components/StarRating/style.css +2 -0
- package/build/components/StarRating/style.css.map +1 -0
- package/build/components/index.js +1 -1
- package/build/components/index.js.map +1 -1
- package/build/components/tsconfig.tsbuildinfo +1 -1
- package/build/components/types/index.d.ts +50 -12
- package/build/components/types/src/components/Avatar/Avatar.d.ts +7 -0
- package/build/components/types/src/components/Avatar/index.d.ts +2 -0
- package/build/components/types/src/components/Carousel/constants.d.ts +2 -0
- package/build/components/types/src/components/Countdown/Countdown.d.ts +10 -0
- package/build/components/types/src/components/Countdown/Countdown.react.d.ts +8 -0
- package/build/components/types/src/components/Countdown/Countdown.shared.d.ts +54 -0
- package/build/components/types/src/components/Countdown/Countdown.static.d.ts +21 -0
- package/build/components/types/src/components/Countdown/CountdownTag.d.ts +9 -0
- package/build/components/types/src/components/Countdown/CountdownTag.static.d.ts +16 -0
- package/build/components/types/src/components/Countdown/constants.d.ts +28 -0
- package/build/components/types/src/components/Countdown/index.d.ts +3 -0
- package/build/components/types/src/components/DocumentationSidebar/DocumentationSidebar.d.ts +1 -0
- package/build/components/types/src/components/Preview/PreviewGenerator.d.ts +1 -0
- package/build/components/types/src/components/PromoBanner/PromoBanner.d.ts +5 -1
- package/build/components/types/src/components/StarRating/StarRating.d.ts +9 -0
- package/build/components/types/src/components/StarRating/index.d.ts +1 -0
- package/build/components/types/src/components/index.d.ts +4 -2
- package/build/components/types/src/scripts/index.d.ts +10 -0
- package/build/components/types/src/styles/export/color.d.ts +2 -0
- package/build/lib/base.css +1 -1
- package/build/lib/base.css.map +1 -1
- package/build/lib/components.css +1 -1
- package/build/lib/components.css.map +1 -1
- package/build/lib/footer.css +1 -1
- package/build/lib/footer.css.map +1 -1
- package/build/lib/megamenu.css +1 -1
- package/build/lib/megamenu.css.map +1 -1
- package/build/lib/scripts.js +1 -1
- package/build/lib/scripts.js.map +1 -1
- package/build/lib/style.css +1 -1
- package/build/lib/style.css.map +1 -1
- package/build/lib/tsconfig.tsbuildinfo +1 -1
- package/build/lib/utilities.css +1 -1
- package/build/lib/utilities.css.map +1 -1
- package/build/sprite.svg +1 -1
- package/package.json +29 -28
- package/src/assets/icons/quote.svg +1 -1
- package/src/assets/icons/star-empty-thin.svg +4 -0
- package/src/components/Avatar/Avatar.tsx +36 -0
- package/src/components/Avatar/index.ts +2 -0
- package/src/components/Avatar/styles/config.scss +11 -0
- package/src/components/Avatar/styles/mixins.scss +53 -0
- package/src/components/Avatar/styles/style.scss +20 -0
- package/src/components/Avatar/tests/Avatar.conformance.test.jsx +23 -0
- package/src/components/Avatar/tests/Avatar.unit.test.jsx +45 -0
- package/src/components/Carousel/Carousel.static.ts +17 -3
- package/src/components/Carousel/Carousel.tsx +28 -24
- package/src/components/Carousel/constants.ts +2 -0
- package/src/components/Carousel/styles/config.scss +5 -8
- package/src/components/Carousel/styles/mixins.scss +48 -78
- package/src/components/Carousel/styles/style.scss +8 -21
- package/src/components/Carousel/tests/Carousel.unit.test.jsx +48 -5
- package/src/components/Countdown/Countdown.react.ts +67 -0
- package/src/components/Countdown/Countdown.shared.ts +228 -0
- package/src/components/Countdown/Countdown.static.ts +201 -0
- package/src/components/Countdown/Countdown.tsx +105 -0
- package/src/components/Countdown/CountdownTag.static.ts +140 -0
- package/src/components/Countdown/CountdownTag.tsx +99 -0
- package/src/components/Countdown/constants.ts +36 -0
- package/src/components/Countdown/index.ts +3 -0
- package/src/components/Countdown/styles/config.scss +17 -0
- package/src/components/Countdown/styles/mixins.scss +193 -0
- package/src/components/Countdown/styles/style.scss +82 -0
- package/src/components/{Testimonial/tests/Testimonial.conformance.test.jsx → Countdown/tests/Countdown.conformance.test.jsx} +7 -2
- package/src/components/Countdown/tests/Countdown.static.test.jsx +117 -0
- package/src/components/Countdown/tests/Countdown.unit.test.jsx +101 -0
- package/src/components/Countdown/tests/CountdownTag.conformance.test.jsx +21 -0
- package/src/components/Countdown/tests/CountdownTag.static.test.jsx +30 -0
- package/src/components/Countdown/tests/CountdownTag.unit.test.jsx +67 -0
- package/src/components/DocumentationSidebar/DocumentationSidebar.tsx +14 -1
- package/src/components/DocumentationSidebar/styles/style.scss +11 -0
- package/src/components/Forms/Autocomplete/Autocomplete.static.ts +14 -1
- package/src/components/Pill/styles/style.scss +2 -1
- package/src/components/Preview/PreviewGenerator.tsx +137 -103
- package/src/components/Preview/styles/style.scss +15 -9
- package/src/components/PromoBanner/PromoBanner.tsx +24 -7
- package/src/components/PromoBanner/tests/PromoBanner.unit.test.jsx +31 -0
- package/src/components/StarRating/StarRating.tsx +52 -0
- package/src/components/StarRating/index.ts +1 -0
- package/src/components/StarRating/styles/style.scss +28 -0
- package/src/components/StarRating/tests/StarRating.unit.test.jsx +62 -0
- package/src/components/index.ts +7 -2
- package/src/styles/export/color.js +2 -0
- package/src/styles/tokens/color.scss +2 -0
- package/src/styles/utilities/text.scss +6 -0
- package/build/components/Testimonial/style.css +0 -2
- package/build/components/Testimonial/style.css.map +0 -1
- package/build/components/types/src/components/Testimonial/Testimonial.d.ts +0 -10
- package/build/components/types/src/components/Testimonial/index.d.ts +0 -1
- package/build/search-index.json +0 -430
- package/src/components/Testimonial/Testimonial.tsx +0 -38
- package/src/components/Testimonial/index.ts +0 -1
- package/src/components/Testimonial/styles/config.scss +0 -42
- package/src/components/Testimonial/styles/mixins.scss +0 -44
- package/src/components/Testimonial/styles/style.scss +0 -16
- package/src/components/Testimonial/tests/Testimonial.unit.test.jsx +0 -43
|
@@ -33,6 +33,19 @@ const addClassToken = (className: string, token: string) => {
|
|
|
33
33
|
return [...classTokens, token].join(" ");
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
const isHtmlElement = (element: Element | null): element is HTMLElement => {
|
|
37
|
+
if (!element) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const htmlElementConstructor =
|
|
42
|
+
element.ownerDocument?.defaultView?.HTMLElement;
|
|
43
|
+
|
|
44
|
+
return htmlElementConstructor
|
|
45
|
+
? element instanceof htmlElementConstructor
|
|
46
|
+
: element.nodeType === Node.ELEMENT_NODE;
|
|
47
|
+
};
|
|
48
|
+
|
|
36
49
|
export default class Autocomplete {
|
|
37
50
|
private element: HTMLElement;
|
|
38
51
|
private config: AutocompleteConfig;
|
|
@@ -139,7 +152,7 @@ export default class Autocomplete {
|
|
|
139
152
|
private getWrapper(): HTMLElement | null {
|
|
140
153
|
if (this.element.tagName === "SELECT") {
|
|
141
154
|
const rootElement = this.element.previousElementSibling;
|
|
142
|
-
if (!(rootElement
|
|
155
|
+
if (!isHtmlElement(rootElement)) {
|
|
143
156
|
return null;
|
|
144
157
|
}
|
|
145
158
|
|
|
@@ -9,7 +9,6 @@ import React, {
|
|
|
9
9
|
useRef,
|
|
10
10
|
useState,
|
|
11
11
|
} from "react";
|
|
12
|
-
import { createPortal } from "react-dom";
|
|
13
12
|
import reactElementToJSXString from "react-element-to-jsx-string";
|
|
14
13
|
import breakpoints from "@/styles/export/breakpoint";
|
|
15
14
|
import { Button } from "../Button";
|
|
@@ -87,57 +86,80 @@ interface PreviewRenderContext {
|
|
|
87
86
|
}
|
|
88
87
|
|
|
89
88
|
interface PreviewBreakpoint {
|
|
89
|
+
deviceLabel?: string;
|
|
90
|
+
frameClassName?: string;
|
|
90
91
|
key: string;
|
|
91
92
|
label: string;
|
|
93
|
+
height: number | null;
|
|
92
94
|
width: number | null;
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
const PREVIEW_BREAKPOINTS: PreviewBreakpoint[] = [
|
|
96
|
-
{
|
|
98
|
+
{
|
|
99
|
+
key: "auto",
|
|
100
|
+
label: "Auto",
|
|
101
|
+
width: null,
|
|
102
|
+
height: null,
|
|
103
|
+
deviceLabel: undefined,
|
|
104
|
+
frameClassName: undefined,
|
|
105
|
+
},
|
|
97
106
|
...Object.entries(breakpoints).map(([key, value]) => {
|
|
98
107
|
const tokenWidth = Number.parseInt(value, 10);
|
|
99
108
|
// `xs` is 0 in tokens (mobile-first min-width), but preview needs a real viewport size.
|
|
100
109
|
const width = key === "xs" ? 320 : tokenWidth;
|
|
110
|
+
const height =
|
|
111
|
+
key === "xs"
|
|
112
|
+
? 640
|
|
113
|
+
: key === "sm"
|
|
114
|
+
? 812
|
|
115
|
+
: key === "md"
|
|
116
|
+
? 1024
|
|
117
|
+
: key === "lg"
|
|
118
|
+
? 900
|
|
119
|
+
: 860;
|
|
120
|
+
const frameClassName =
|
|
121
|
+
key === "xs" || key === "sm"
|
|
122
|
+
? "isPhone"
|
|
123
|
+
: key === "md"
|
|
124
|
+
? "isTablet"
|
|
125
|
+
: "isDesktop";
|
|
126
|
+
const deviceLabel =
|
|
127
|
+
key === "xs"
|
|
128
|
+
? "Phone S"
|
|
129
|
+
: key === "sm"
|
|
130
|
+
? "Phone L"
|
|
131
|
+
: key === "md"
|
|
132
|
+
? "Tablet"
|
|
133
|
+
: key === "lg"
|
|
134
|
+
? "Laptop"
|
|
135
|
+
: "Desktop";
|
|
101
136
|
|
|
102
137
|
return {
|
|
138
|
+
deviceLabel,
|
|
139
|
+
frameClassName,
|
|
103
140
|
key,
|
|
104
|
-
|
|
141
|
+
height,
|
|
142
|
+
label: `${deviceLabel} (${width} x ${height})`,
|
|
105
143
|
width,
|
|
106
144
|
};
|
|
107
145
|
}),
|
|
108
146
|
];
|
|
109
147
|
|
|
110
|
-
const IFRAME_VIEWPORT_GUTTER = 16;
|
|
111
|
-
const getScreenLikeMinHeight = (width: number | null): number => {
|
|
112
|
-
if (width === null) {
|
|
113
|
-
return 1;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (width <= 480) {
|
|
117
|
-
return 700;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (width <= 768) {
|
|
121
|
-
return 640;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return 560;
|
|
125
|
-
};
|
|
126
|
-
|
|
127
148
|
interface PreviewViewportFrameProps {
|
|
128
|
-
children: ReactNode;
|
|
129
149
|
className?: string;
|
|
130
|
-
|
|
150
|
+
frameClassName?: string;
|
|
151
|
+
html: string;
|
|
152
|
+
height?: number;
|
|
131
153
|
}
|
|
132
154
|
|
|
133
155
|
const PreviewViewportFrame: React.FC<PreviewViewportFrameProps> = ({
|
|
134
|
-
children,
|
|
135
156
|
className,
|
|
136
|
-
|
|
157
|
+
frameClassName,
|
|
158
|
+
height = 1,
|
|
159
|
+
html,
|
|
137
160
|
}) => {
|
|
138
161
|
const iframeRef = useRef<HTMLIFrameElement | null>(null);
|
|
139
|
-
const [
|
|
140
|
-
const [height, setHeight] = useState(1);
|
|
162
|
+
const [isIframeScriptsReady, setIsIframeScriptsReady] = useState(false);
|
|
141
163
|
|
|
142
164
|
useEffect(() => {
|
|
143
165
|
const iframe = iframeRef.current;
|
|
@@ -148,11 +170,11 @@ const PreviewViewportFrame: React.FC<PreviewViewportFrameProps> = ({
|
|
|
148
170
|
|
|
149
171
|
doc.open();
|
|
150
172
|
doc.write(
|
|
151
|
-
`<!doctype html><html><head
|
|
173
|
+
`<!doctype html><html><head><meta name="viewport" content="width=device-width, initial-scale=1" /></head><body><div id="preview-iframe-root"></div></body></html>`,
|
|
152
174
|
);
|
|
153
175
|
doc.close();
|
|
154
176
|
|
|
155
|
-
// Mirror parent styles so components inside iframe have
|
|
177
|
+
// Mirror parent styles so components inside iframe have same look.
|
|
156
178
|
const stylesToClone = document.querySelectorAll(
|
|
157
179
|
'link[rel="stylesheet"], style',
|
|
158
180
|
);
|
|
@@ -164,78 +186,60 @@ const PreviewViewportFrame: React.FC<PreviewViewportFrameProps> = ({
|
|
|
164
186
|
doc.body.style.margin = "0";
|
|
165
187
|
doc.body.style.padding = "0";
|
|
166
188
|
doc.body.style.background = "transparent";
|
|
167
|
-
doc.body.style.overflow = "
|
|
189
|
+
doc.body.style.overflow = "auto";
|
|
168
190
|
doc.documentElement.style.margin = "0";
|
|
169
191
|
doc.documentElement.style.padding = "0";
|
|
170
|
-
doc.documentElement.style.
|
|
192
|
+
doc.documentElement.style.height = "100%";
|
|
193
|
+
doc.documentElement.style.overflow = "auto";
|
|
194
|
+
doc.body.style.height = "100%";
|
|
171
195
|
|
|
172
|
-
iframe.setAttribute("scrolling", "
|
|
196
|
+
iframe.setAttribute("scrolling", "auto");
|
|
173
197
|
|
|
174
198
|
const root = doc.getElementById("preview-iframe-root");
|
|
175
199
|
if (!root) return;
|
|
176
200
|
|
|
201
|
+
const script = doc.createElement("script");
|
|
202
|
+
script.src = "/ods-preview-scripts.js";
|
|
203
|
+
script.addEventListener("load", () => {
|
|
204
|
+
setIsIframeScriptsReady(true);
|
|
205
|
+
});
|
|
206
|
+
doc.head.appendChild(script);
|
|
207
|
+
|
|
177
208
|
root.style.width = "100%";
|
|
178
|
-
root.style.minHeight = "
|
|
209
|
+
root.style.minHeight = "100%";
|
|
179
210
|
root.style.boxSizing = "border-box";
|
|
180
|
-
root.style.paddingInline = `${IFRAME_VIEWPORT_GUTTER}px`;
|
|
181
211
|
root.style.overflow = "visible";
|
|
182
|
-
setIframeRoot(root);
|
|
183
212
|
}, []);
|
|
184
213
|
|
|
185
214
|
useEffect(() => {
|
|
186
|
-
|
|
215
|
+
const root = iframeRef.current?.contentDocument?.getElementById(
|
|
216
|
+
"preview-iframe-root",
|
|
217
|
+
);
|
|
187
218
|
|
|
188
|
-
|
|
189
|
-
const frameWindow = doc.defaultView;
|
|
219
|
+
if (!root) return;
|
|
190
220
|
|
|
191
|
-
|
|
192
|
-
|
|
221
|
+
root.innerHTML = html;
|
|
222
|
+
}, [html]);
|
|
193
223
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const nodeRect = node.getBoundingClientRect();
|
|
199
|
-
if (nodeRect.bottom > maxDescendantBottom) {
|
|
200
|
-
maxDescendantBottom = nodeRect.bottom;
|
|
201
|
-
}
|
|
202
|
-
});
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
const iframe = iframeRef.current;
|
|
226
|
+
const doc = iframe?.contentDocument;
|
|
227
|
+
if (!doc || !isIframeScriptsReady) return;
|
|
203
228
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
// Update only when there is a meaningful delta to avoid feedback loops.
|
|
212
|
-
setHeight((prevHeight) =>
|
|
213
|
-
Math.abs(prevHeight - nextHeight) <= 1 ? prevHeight : nextHeight,
|
|
214
|
-
);
|
|
215
|
-
};
|
|
229
|
+
const frameWindow = doc.defaultView as
|
|
230
|
+
| (Window & {
|
|
231
|
+
ODS?: {
|
|
232
|
+
initModulesWithin?: (container: ParentNode) => void;
|
|
233
|
+
};
|
|
234
|
+
})
|
|
235
|
+
| null;
|
|
216
236
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const timeoutId = frameWindow?.setTimeout(updateHeight, 100);
|
|
221
|
-
const resizeObserver =
|
|
222
|
-
typeof ResizeObserver !== "undefined"
|
|
223
|
-
? new ResizeObserver(updateHeight)
|
|
224
|
-
: null;
|
|
225
|
-
resizeObserver?.observe(iframeRoot);
|
|
226
|
-
|
|
227
|
-
const mutationObserver =
|
|
228
|
-
typeof MutationObserver !== "undefined"
|
|
229
|
-
? new MutationObserver(updateHeight)
|
|
230
|
-
: null;
|
|
231
|
-
mutationObserver?.observe(iframeRoot, {
|
|
232
|
-
subtree: true,
|
|
233
|
-
childList: true,
|
|
234
|
-
attributes: true,
|
|
235
|
-
characterData: true,
|
|
236
|
-
});
|
|
237
|
+
const init = () => {
|
|
238
|
+
frameWindow?.ODS?.initModulesWithin?.(doc);
|
|
239
|
+
};
|
|
237
240
|
|
|
238
|
-
frameWindow?.
|
|
241
|
+
const rafId = frameWindow?.requestAnimationFrame(init);
|
|
242
|
+
const timeoutId = frameWindow?.setTimeout(init, 0);
|
|
239
243
|
|
|
240
244
|
return () => {
|
|
241
245
|
if (typeof rafId === "number" && frameWindow) {
|
|
@@ -244,22 +248,16 @@ const PreviewViewportFrame: React.FC<PreviewViewportFrameProps> = ({
|
|
|
244
248
|
if (typeof timeoutId === "number" && frameWindow) {
|
|
245
249
|
frameWindow.clearTimeout(timeoutId);
|
|
246
250
|
}
|
|
247
|
-
resizeObserver?.disconnect();
|
|
248
|
-
mutationObserver?.disconnect();
|
|
249
|
-
frameWindow?.removeEventListener("resize", updateHeight);
|
|
250
251
|
};
|
|
251
|
-
}, [
|
|
252
|
+
}, [html, isIframeScriptsReady]);
|
|
252
253
|
|
|
253
254
|
return (
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
/>
|
|
261
|
-
{iframeRoot && createPortal(children, iframeRoot)}
|
|
262
|
-
</>
|
|
255
|
+
<iframe
|
|
256
|
+
ref={iframeRef}
|
|
257
|
+
title="Preview viewport"
|
|
258
|
+
className={cx("previewViewportFrame", className, frameClassName)}
|
|
259
|
+
style={{ height }}
|
|
260
|
+
/>
|
|
263
261
|
);
|
|
264
262
|
};
|
|
265
263
|
|
|
@@ -270,6 +268,7 @@ interface PreviewProps {
|
|
|
270
268
|
bgThemeExcludedColors?: string[];
|
|
271
269
|
children?: ReactNode | ((context: PreviewRenderContext) => ReactNode);
|
|
272
270
|
className?: string;
|
|
271
|
+
preserveDividerSpacing?: boolean;
|
|
273
272
|
code?: ReactNode | string | ((context: PreviewRenderContext) => ReactNode);
|
|
274
273
|
codeJSXOptions?: Record<string, any>;
|
|
275
274
|
codeTypes?: string[];
|
|
@@ -344,6 +343,7 @@ const PreviewGenerator: FunctionComponent<PreviewProps> = ({
|
|
|
344
343
|
bgThemeExcludedColors = [],
|
|
345
344
|
children,
|
|
346
345
|
className,
|
|
346
|
+
preserveDividerSpacing = false,
|
|
347
347
|
code,
|
|
348
348
|
codeJSXOptions,
|
|
349
349
|
codeTypes = ["html", "jsx"],
|
|
@@ -360,6 +360,8 @@ const PreviewGenerator: FunctionComponent<PreviewProps> = ({
|
|
|
360
360
|
const [isCodeShown, setIsCodeShown] = useState(false);
|
|
361
361
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
362
362
|
const [isDarkMode, setIsDarkMode] = useState(isDark);
|
|
363
|
+
const [iframeMarkup, setIframeMarkup] = useState(html || "");
|
|
364
|
+
const iframeMarkupSourceRef = useRef<HTMLDivElement | null>(null);
|
|
363
365
|
// Ensure unique dropdown id per Preview instance to avoid id collisions (SSR-safe)
|
|
364
366
|
const backgroundDropdownId = `background-select-${useId()}`;
|
|
365
367
|
const breakpointDropdownId = `breakpoint-select-${useId()}`;
|
|
@@ -457,6 +459,9 @@ const PreviewGenerator: FunctionComponent<PreviewProps> = ({
|
|
|
457
459
|
const wrapperClasses = cx("previewWrapper", {
|
|
458
460
|
isFullscreen: isFullscreen,
|
|
459
461
|
});
|
|
462
|
+
const liveCardClasses = cx(classes, {
|
|
463
|
+
"preview--preserve-divider-spacing": preserveDividerSpacing,
|
|
464
|
+
});
|
|
460
465
|
|
|
461
466
|
const actions: ReactNode[] = [];
|
|
462
467
|
|
|
@@ -628,16 +633,35 @@ const PreviewGenerator: FunctionComponent<PreviewProps> = ({
|
|
|
628
633
|
// Use toCode for HTML rendering
|
|
629
634
|
const codeForHTML = codeContent;
|
|
630
635
|
|
|
636
|
+
useEffect(() => {
|
|
637
|
+
if (html) {
|
|
638
|
+
setIframeMarkup(html);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (iframeMarkupSourceRef.current) {
|
|
643
|
+
setIframeMarkup(iframeMarkupSourceRef.current.innerHTML);
|
|
644
|
+
}
|
|
645
|
+
}, [
|
|
646
|
+
childrenToRender,
|
|
647
|
+
html,
|
|
648
|
+
isDarkMode,
|
|
649
|
+
previewBackground,
|
|
650
|
+
previewBreakpoint,
|
|
651
|
+
]);
|
|
652
|
+
|
|
631
653
|
const previewViewportWidth = disableBreakpoints
|
|
632
654
|
? null
|
|
633
655
|
: previewBreakpoint.width;
|
|
634
|
-
const
|
|
656
|
+
const previewViewportHeight = disableBreakpoints
|
|
657
|
+
? null
|
|
658
|
+
: previewBreakpoint.height;
|
|
635
659
|
const shouldUseIframeViewport = previewViewportWidth !== null;
|
|
636
660
|
|
|
637
661
|
return (
|
|
638
662
|
<div className={wrapperClasses}>
|
|
639
663
|
<PreviewTitleBar actions={actions} />
|
|
640
|
-
<Card className={
|
|
664
|
+
<Card className={liveCardClasses} {...other}>
|
|
641
665
|
<div
|
|
642
666
|
className={cx("previewLiveViewportArea", {
|
|
643
667
|
hasFixedViewport: shouldUseIframeViewport,
|
|
@@ -657,13 +681,23 @@ const PreviewGenerator: FunctionComponent<PreviewProps> = ({
|
|
|
657
681
|
{!shouldUseIframeViewport ? (
|
|
658
682
|
<div className="previewLive">{toRender}</div>
|
|
659
683
|
) : (
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
684
|
+
<>
|
|
685
|
+
<div
|
|
686
|
+
ref={iframeMarkupSourceRef}
|
|
687
|
+
hidden
|
|
688
|
+
aria-hidden="true"
|
|
689
|
+
style={{ display: "none" }}
|
|
690
|
+
>
|
|
691
|
+
<div className="previewLive">{toRender}</div>
|
|
692
|
+
</div>
|
|
693
|
+
<PreviewViewportFrame
|
|
694
|
+
key={previewBreakpoint.key}
|
|
695
|
+
className={themeClass}
|
|
696
|
+
frameClassName={previewBreakpoint.frameClassName}
|
|
697
|
+
height={previewViewportHeight ?? 1}
|
|
698
|
+
html={iframeMarkup}
|
|
699
|
+
/>
|
|
700
|
+
</>
|
|
667
701
|
)}
|
|
668
702
|
</div>
|
|
669
703
|
</div>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
@use "../../../styles/tokens/breakpoint";
|
|
1
2
|
@use "../../../styles/tokens/color" as *;
|
|
3
|
+
@use "../../../styles/tokens/space";
|
|
2
4
|
|
|
3
5
|
@layer components {
|
|
4
6
|
// ===== PREVIEW GRID STYLES =====
|
|
@@ -61,6 +63,18 @@
|
|
|
61
63
|
overflow: visible !important;
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
.card.preview--preserve-divider-spacing {
|
|
67
|
+
.divider {
|
|
68
|
+
padding-top: space.get();
|
|
69
|
+
padding-bottom: space.get();
|
|
70
|
+
|
|
71
|
+
@include breakpoint.get("md") {
|
|
72
|
+
padding-top: space.get("large");
|
|
73
|
+
padding-bottom: space.get("large");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
64
78
|
.previewLive {
|
|
65
79
|
transition: all 200ms ease-in-out;
|
|
66
80
|
overflow: visible;
|
|
@@ -82,13 +96,6 @@
|
|
|
82
96
|
|
|
83
97
|
.previewLiveViewport {
|
|
84
98
|
background: var(--color-background-primary);
|
|
85
|
-
border: 1px solid
|
|
86
|
-
color-mix(in srgb, var(--color-border-strong), transparent 25%);
|
|
87
|
-
box-shadow:
|
|
88
|
-
0 0 0 1px
|
|
89
|
-
color-mix(in srgb, var(--color-border-strong), transparent 60%),
|
|
90
|
-
0 12px 28px
|
|
91
|
-
color-mix(in srgb, var(--color-text-default), transparent 88%);
|
|
92
99
|
}
|
|
93
100
|
}
|
|
94
101
|
}
|
|
@@ -106,8 +113,7 @@
|
|
|
106
113
|
width: 100%;
|
|
107
114
|
border: 0;
|
|
108
115
|
display: block;
|
|
109
|
-
background:
|
|
110
|
-
padding-top: 16px;
|
|
116
|
+
background: var(--color-background-primary);
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
.selectWrapper {
|
|
@@ -3,9 +3,14 @@ import React from "react";
|
|
|
3
3
|
import { Grid, GridCol } from "../Grid";
|
|
4
4
|
import { Image } from "../Image";
|
|
5
5
|
|
|
6
|
+
interface PromoBannerImageSrc {
|
|
7
|
+
src: string;
|
|
8
|
+
srcSet?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
interface PromoBannerProps {
|
|
7
12
|
className?: string;
|
|
8
|
-
image?: string;
|
|
13
|
+
image?: string | PromoBannerImageSrc;
|
|
9
14
|
alignImage?: "bottom" | "top";
|
|
10
15
|
/** Remove top padding from item on sm and smaller breakpoints */
|
|
11
16
|
flushTopSmDown?: boolean;
|
|
@@ -21,6 +26,15 @@ interface PromoBannerProps {
|
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
const CLASS_ROOT = "promo-banner";
|
|
29
|
+
|
|
30
|
+
const getImageProps = (image?: string | PromoBannerImageSrc) => {
|
|
31
|
+
if (!image) return null;
|
|
32
|
+
|
|
33
|
+
return typeof image === "string"
|
|
34
|
+
? { src: image }
|
|
35
|
+
: { src: image.src, srcSet: image.srcSet };
|
|
36
|
+
};
|
|
37
|
+
|
|
24
38
|
export const PromoBanner = ({
|
|
25
39
|
image,
|
|
26
40
|
className,
|
|
@@ -35,16 +49,17 @@ export const PromoBanner = ({
|
|
|
35
49
|
imageFullWidth,
|
|
36
50
|
...other
|
|
37
51
|
}: PromoBannerProps) => {
|
|
52
|
+
const imageProps = getImageProps(image);
|
|
38
53
|
const classes = cx(CLASS_ROOT, className, {
|
|
39
54
|
[`${CLASS_ROOT}--flush-top-sm-down`]: flushTopSmDown,
|
|
40
55
|
[`${CLASS_ROOT}--flush-bottom-sm-down`]: flushBottomSmDown,
|
|
41
|
-
[`${CLASS_ROOT}--reverse`]: variant === "reverse" && !!
|
|
56
|
+
[`${CLASS_ROOT}--reverse`]: variant === "reverse" && !!imageProps?.src,
|
|
42
57
|
[`${CLASS_ROOT}--vertical`]: variant === "vertical",
|
|
43
58
|
});
|
|
44
59
|
return (
|
|
45
60
|
<div className={classes} {...other}>
|
|
46
61
|
<Grid columnGapSize="large">
|
|
47
|
-
{variant === "reverse" &&
|
|
62
|
+
{variant === "reverse" && imageProps?.src && (
|
|
48
63
|
<GridCol
|
|
49
64
|
size={{ xs: 12, md: 6 }}
|
|
50
65
|
className={cx(
|
|
@@ -54,7 +69,8 @@ export const PromoBanner = ({
|
|
|
54
69
|
)}
|
|
55
70
|
>
|
|
56
71
|
<Image
|
|
57
|
-
src={
|
|
72
|
+
src={imageProps.src}
|
|
73
|
+
srcSet={imageProps.srcSet}
|
|
58
74
|
alt="Obrázok"
|
|
59
75
|
className={cx("mb-none", { fullwidth: imageFullWidth })}
|
|
60
76
|
/>
|
|
@@ -65,7 +81,7 @@ export const PromoBanner = ({
|
|
|
65
81
|
size={
|
|
66
82
|
variant === "vertical"
|
|
67
83
|
? { xs: 12, sm: 10, md: 8, lg: 6 }
|
|
68
|
-
: { xs: 12, md:
|
|
84
|
+
: { xs: 12, md: imageProps?.src ? 6 : 7 }
|
|
69
85
|
}
|
|
70
86
|
className={cx(
|
|
71
87
|
`${CLASS_ROOT}__item`,
|
|
@@ -79,7 +95,7 @@ export const PromoBanner = ({
|
|
|
79
95
|
{children}
|
|
80
96
|
</GridCol>
|
|
81
97
|
|
|
82
|
-
{
|
|
98
|
+
{imageProps?.src && variant !== "reverse" && (
|
|
83
99
|
<GridCol
|
|
84
100
|
size={variant === "vertical" ? { xs: 12 } : { xs: 12, md: 6 }}
|
|
85
101
|
className={cx(
|
|
@@ -95,7 +111,8 @@ export const PromoBanner = ({
|
|
|
95
111
|
)}
|
|
96
112
|
>
|
|
97
113
|
<Image
|
|
98
|
-
src={
|
|
114
|
+
src={imageProps.src}
|
|
115
|
+
srcSet={imageProps.srcSet}
|
|
99
116
|
alt="Obrázok"
|
|
100
117
|
className={cx("mb-none", { fullwidth: imageFullWidth })}
|
|
101
118
|
/>
|
|
@@ -315,6 +315,37 @@ describe("PromoBanner", () => {
|
|
|
315
315
|
expect(image).toHaveAttribute("src", "https://example.com/test.jpg");
|
|
316
316
|
});
|
|
317
317
|
|
|
318
|
+
it("renders image from object src when image object is provided", () => {
|
|
319
|
+
render(
|
|
320
|
+
<PromoBanner
|
|
321
|
+
image={{ src: "https://example.com/test.jpg", srcSet: "test 1x" }}
|
|
322
|
+
>
|
|
323
|
+
Content
|
|
324
|
+
</PromoBanner>,
|
|
325
|
+
);
|
|
326
|
+
const image = screen.getByRole("img");
|
|
327
|
+
expect(image).toHaveAttribute("src", "https://example.com/test.jpg");
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("passes srcSet to Image when image object is provided", () => {
|
|
331
|
+
render(
|
|
332
|
+
<PromoBanner
|
|
333
|
+
image={{
|
|
334
|
+
src: "https://example.com/test.jpg",
|
|
335
|
+
srcSet:
|
|
336
|
+
"https://example.com/test.jpg 1x, https://example.com/test@2x.jpg 2x",
|
|
337
|
+
}}
|
|
338
|
+
>
|
|
339
|
+
Content
|
|
340
|
+
</PromoBanner>,
|
|
341
|
+
);
|
|
342
|
+
const image = screen.getByRole("img");
|
|
343
|
+
expect(image).toHaveAttribute(
|
|
344
|
+
"srcSet",
|
|
345
|
+
"https://example.com/test.jpg 1x, https://example.com/test@2x.jpg 2x",
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
|
|
318
349
|
it("applies default alt text", () => {
|
|
319
350
|
render(<PromoBanner image="test.jpg">Content</PromoBanner>);
|
|
320
351
|
const image = screen.getByRole("img");
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import cx from "classnames";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
import { Icon } from "../Icon";
|
|
5
|
+
|
|
6
|
+
export interface StarRatingProps
|
|
7
|
+
extends Omit<React.HTMLAttributes<HTMLSpanElement>, "children"> {
|
|
8
|
+
/** Extra class name for root element. */
|
|
9
|
+
className?: string;
|
|
10
|
+
/** Rating value. Rounded to nearest whole star. */
|
|
11
|
+
rating: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const CLASS_ROOT = "star-rating";
|
|
15
|
+
const STAR_COUNT = 5;
|
|
16
|
+
const FILLED_ICON_NAME = "star-favourite";
|
|
17
|
+
const EMPTY_ICON_NAME = "star-empty-thin";
|
|
18
|
+
|
|
19
|
+
const clampRating = (rating: number) =>
|
|
20
|
+
Math.max(0, Math.min(Math.round(rating), STAR_COUNT));
|
|
21
|
+
|
|
22
|
+
const StarRating: React.FC<StarRatingProps> = ({
|
|
23
|
+
className,
|
|
24
|
+
rating,
|
|
25
|
+
...other
|
|
26
|
+
}) => {
|
|
27
|
+
const filledStars = clampRating(rating);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<span className={cx(CLASS_ROOT, className)} {...other}>
|
|
31
|
+
{Array.from({ length: STAR_COUNT }, (_, index) => {
|
|
32
|
+
const isFilled = index < filledStars;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Icon
|
|
36
|
+
key={`${isFilled ? FILLED_ICON_NAME : EMPTY_ICON_NAME}-${index}`}
|
|
37
|
+
className={cx(`${CLASS_ROOT}__icon`, {
|
|
38
|
+
[`${CLASS_ROOT}__icon--filled`]: isFilled,
|
|
39
|
+
[`${CLASS_ROOT}__icon--empty`]: !isFilled,
|
|
40
|
+
})}
|
|
41
|
+
name={isFilled ? FILLED_ICON_NAME : EMPTY_ICON_NAME}
|
|
42
|
+
size="large"
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
})}
|
|
46
|
+
</span>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
StarRating.displayName = "StarRating";
|
|
51
|
+
|
|
52
|
+
export { StarRating };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { StarRating } from "./StarRating";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
@use "../../../styles/tools/convert";
|
|
2
|
+
@use "../../../styles/tokens/space";
|
|
3
|
+
@use "../../../styles/tokens/breakpoint";
|
|
4
|
+
|
|
5
|
+
@layer components {
|
|
6
|
+
.star-rating {
|
|
7
|
+
display: flex;
|
|
8
|
+
margin-bottom: space.get("large");
|
|
9
|
+
width: fit-content;
|
|
10
|
+
gap: convert.to-rem(5px);
|
|
11
|
+
|
|
12
|
+
&__icon--filled {
|
|
13
|
+
color: var(--color-icon-brand);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
&__icon--empty {
|
|
17
|
+
color: var(--color-icon-disabled);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&__icon {
|
|
21
|
+
--icon-size: #{convert.to-rem(24px)};
|
|
22
|
+
|
|
23
|
+
@include breakpoint.get(md) {
|
|
24
|
+
--icon-size: #{convert.to-rem(32px)};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|