@dnanpm/styleguide 3.12.0 → 3.12.2

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.
Files changed (39) hide show
  1. package/build/cjs/components/Breadcrumb/Breadcrumb.d.ts +42 -0
  2. package/build/cjs/components/Breadcrumb/Breadcrumb.js +90 -0
  3. package/build/cjs/components/Carousel/Carousel.d.ts +11 -14
  4. package/build/cjs/components/Carousel/Carousel.js +52 -40
  5. package/build/cjs/components/DateTimePicker/DateTimePicker.js +4 -0
  6. package/build/cjs/components/Hero/Hero.d.ts +0 -6
  7. package/build/cjs/components/Hero/Hero.js +3 -3
  8. package/build/cjs/components/NotificationBadge/NotificationBadge.js +4 -0
  9. package/build/cjs/components/PriorityNavigation/PriorityNavigation.js +4 -0
  10. package/build/cjs/components/Skeleton/Skeleton.d.ts +63 -0
  11. package/build/cjs/components/Skeleton/Skeleton.js +73 -0
  12. package/build/cjs/components/Tooltip/Tooltip.js +1 -1
  13. package/build/cjs/components/index.d.ts +2 -0
  14. package/build/cjs/index.js +4 -0
  15. package/build/cjs/themes/globalStyles.js +1 -0
  16. package/build/cjs/themes/theme.d.ts +9 -2
  17. package/build/cjs/themes/themeComponents/breakpoints.d.ts +9 -4
  18. package/build/cjs/utils/styledUtils.d.ts +22 -1
  19. package/build/cjs/utils/styledUtils.js +26 -6
  20. package/build/es/components/Breadcrumb/Breadcrumb.d.ts +42 -0
  21. package/build/es/components/Breadcrumb/Breadcrumb.js +82 -0
  22. package/build/es/components/Carousel/Carousel.d.ts +11 -14
  23. package/build/es/components/Carousel/Carousel.js +52 -40
  24. package/build/es/components/DateTimePicker/DateTimePicker.js +4 -0
  25. package/build/es/components/Hero/Hero.d.ts +0 -6
  26. package/build/es/components/Hero/Hero.js +3 -3
  27. package/build/es/components/NotificationBadge/NotificationBadge.js +4 -0
  28. package/build/es/components/PriorityNavigation/PriorityNavigation.js +4 -0
  29. package/build/es/components/Skeleton/Skeleton.d.ts +63 -0
  30. package/build/es/components/Skeleton/Skeleton.js +65 -0
  31. package/build/es/components/Tooltip/Tooltip.js +1 -1
  32. package/build/es/components/index.d.ts +2 -0
  33. package/build/es/index.js +2 -0
  34. package/build/es/themes/globalStyles.js +1 -0
  35. package/build/es/themes/theme.d.ts +9 -2
  36. package/build/es/themes/themeComponents/breakpoints.d.ts +9 -4
  37. package/build/es/utils/styledUtils.d.ts +22 -1
  38. package/build/es/utils/styledUtils.js +26 -6
  39. package/package.json +14 -10
@@ -1,3 +1,4 @@
1
+ import { css } from '../themes/styled';
1
2
  export declare const getMultipliedSize: (base: {
2
3
  value: number;
3
4
  unit: string;
@@ -6,4 +7,24 @@ export declare const getDividedSize: (base: {
6
7
  value: number;
7
8
  unit: string;
8
9
  }, divide: number) => string;
9
- export declare const media: Record<string | number, (l: TemplateStringsArray, ...p: (string | number)[]) => string>;
10
+ /**
11
+ * Media query helpers for responsive design.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const StyledDiv = styled.div`
16
+ * font-size: 1rem;
17
+ * ${media.md`font-size: 1.2rem;`}
18
+ * ${media.lg`font-size: 1.5rem;`}
19
+ * `;
20
+ * ```
21
+ *
22
+ * Available breakpoints:
23
+ * - `xs`: 480px
24
+ * - `sm`: 576px
25
+ * - `md`: 768px
26
+ * - `lg`: 992px
27
+ * - `xl`: 1200px
28
+ * - `xxl`: 1440px
29
+ */
30
+ export declare const media: Record<"xxl" | "xl" | "lg" | "md" | "sm" | "xs", (l: TemplateStringsArray, ...p: (string | number)[]) => ReturnType<typeof css>>;
@@ -5,12 +5,32 @@ var breakpoints = require('../themes/themeComponents/breakpoints.js');
5
5
 
6
6
  const getMultipliedSize = (base, multiply) => `${multiply * base.value}${base.unit}`;
7
7
  const getDividedSize = (base, divide) => `${base.value / divide}${base.unit}`;
8
- const media = Object.keys(breakpoints.default).reduce((acc, label) => {
9
- acc[label] = (literals, ...placeholders) => styledComponents.css `
10
- @media (min-width: ${breakpoints.default[label]}px) {
11
- ${styledComponents.css(literals, ...placeholders)};
12
- }
13
- `.join('');
8
+ /**
9
+ * Media query helpers for responsive design.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const StyledDiv = styled.div`
14
+ * font-size: 1rem;
15
+ * ${media.md`font-size: 1.2rem;`}
16
+ * ${media.lg`font-size: 1.5rem;`}
17
+ * `;
18
+ * ```
19
+ *
20
+ * Available breakpoints:
21
+ * - `xs`: 480px
22
+ * - `sm`: 576px
23
+ * - `md`: 768px
24
+ * - `lg`: 992px
25
+ * - `xl`: 1200px
26
+ * - `xxl`: 1440px
27
+ */
28
+ const media = Object.keys(breakpoints.default).reduce((acc, key) => {
29
+ acc[key] = (literals, ...placeholders) => styledComponents.css `
30
+ @media (min-width: ${breakpoints.default[key]}px) {
31
+ ${styledComponents.css(literals, ...placeholders)}
32
+ }
33
+ `;
14
34
  return acc;
15
35
  }, {});
16
36
 
@@ -0,0 +1,42 @@
1
+ import type { ComponentType } from 'react';
2
+ import React from 'react';
3
+ export interface BreadcrumbItem {
4
+ /**
5
+ * Display text for the breadcrumb item
6
+ */
7
+ label: string;
8
+ /**
9
+ * URL/path for the breadcrumb item. If not provided, item will be rendered as text only
10
+ */
11
+ href?: string;
12
+ }
13
+ interface Props {
14
+ /**
15
+ * Array of breadcrumb items to display
16
+ */
17
+ items?: BreadcrumbItem[];
18
+ /**
19
+ * Custom link component to use instead of default anchor element
20
+ * Useful for router integration (e.g., Next.js Link, React Router Link)
21
+ */
22
+ linkComponent?: ComponentType<any>;
23
+ /**
24
+ * Props to pass to the link component
25
+ */
26
+ linkProps?: Record<string, unknown>;
27
+ /**
28
+ * Screen reader label describing the breadcrumb navigation
29
+ */
30
+ ariaLabel?: string;
31
+ /**
32
+ * Allows to pass testid string for testing purposes
33
+ */
34
+ 'data-testid'?: string;
35
+ /**
36
+ * Allows to pass a custom className
37
+ */
38
+ className?: string;
39
+ }
40
+ declare const Breadcrumb: ({ "data-testid": dataTestId, ariaLabel, className, items, linkComponent: LinkComponent, linkProps, }: Props) => React.JSX.Element | null;
41
+ /** @component */
42
+ export default Breadcrumb;
@@ -0,0 +1,82 @@
1
+ import React__default from 'react';
2
+ import { ChevronRight } from '@dnanpm/icons';
3
+ import { styled } from 'styled-components';
4
+ import theme from '../../themes/theme.js';
5
+ import { getMultipliedSize } from '../../utils/styledUtils.js';
6
+
7
+ const BreadcrumbNav = styled.nav `
8
+ font-size: ${theme.fontSize.s};
9
+ font-weight: ${theme.fontWeight.medium};
10
+ `;
11
+ const BreadcrumbList = styled.ol `
12
+ display: flex;
13
+ align-items: center;
14
+ flex-wrap: nowrap;
15
+ list-style: none;
16
+ margin: ${getMultipliedSize(theme.base.baseHeight, 2)} 0;
17
+ padding: 0;
18
+ gap: ${getMultipliedSize(theme.base.baseHeight, 0.5)};
19
+ overflow: visible;
20
+ container: breadcrumb / inline-size;
21
+
22
+ /* Responsive behavior: show only last 2 items when container < 600px */
23
+ @container (max-width: 599px) {
24
+ li:not(:nth-last-child(-n + 2)) {
25
+ display: none;
26
+ }
27
+ }
28
+ `;
29
+ const BreadcrumbListItem = styled.li `
30
+ display: flex;
31
+ align-items: center;
32
+ gap: ${getMultipliedSize(theme.base.baseHeight, 0.5)};
33
+
34
+ &:last-child {
35
+ min-width: 0;
36
+ }
37
+
38
+ a {
39
+ &:focus-visible {
40
+ outline: none;
41
+ border-radius: ${theme.radius.s};
42
+ box-shadow:
43
+ 0px 0px 0px 2px ${theme.color.focus.light},
44
+ 0px 0px 0px 4px ${theme.color.focus.dark};
45
+ }
46
+ }
47
+
48
+ span {
49
+ flex: 1 1 0%;
50
+ white-space: nowrap;
51
+ overflow: hidden;
52
+ text-overflow: ellipsis;
53
+ }
54
+ `;
55
+ const Breadcrumb = ({ 'data-testid': dataTestId, ariaLabel, className, items, linkComponent: LinkComponent, linkProps = {}, }) => {
56
+ if (!items || items.length === 0) {
57
+ return null;
58
+ }
59
+ const renderItem = (item, index) => {
60
+ const isLastItem = index === items.length - 1;
61
+ if (isLastItem || !item.href) {
62
+ return React__default.createElement("span", { "aria-current": isLastItem ? 'page' : undefined }, item.label);
63
+ }
64
+ if (LinkComponent) {
65
+ return (React__default.createElement(LinkComponent, Object.assign({ href: item.href, itemProp: "item", itemScope: true, itemType: "https://schema.org/WebPage" }, linkProps),
66
+ React__default.createElement("span", { itemProp: "name" }, item.label)));
67
+ }
68
+ return (React__default.createElement("a", { href: item.href, itemProp: "item", itemScope: true, itemType: "https://schema.org/WebPage" },
69
+ React__default.createElement("span", { itemProp: "name" }, item.label)));
70
+ };
71
+ return (React__default.createElement(BreadcrumbNav, { "aria-label": ariaLabel, className: className, "data-testid": dataTestId },
72
+ React__default.createElement(BreadcrumbList, { itemScope: true, itemType: "https://schema.org/BreadcrumbList" }, items.map((item, index) => {
73
+ var _a;
74
+ const isLastItem = index === items.length - 1;
75
+ return (React__default.createElement(BreadcrumbListItem, { itemProp: "itemListElement", itemScope: true, itemType: "https://schema.org/ListItem", key: `breadcrumb-${item.label}-${(_a = item.href) !== null && _a !== void 0 ? _a : 'nolink'}` },
76
+ renderItem(item, index),
77
+ React__default.createElement("meta", { itemProp: "position", content: (index + 1).toString() }),
78
+ !isLastItem && (React__default.createElement(ChevronRight, { color: theme.color.background.pink.default, size: "0.9rem" }))));
79
+ }))));
80
+ };
81
+
82
+ export { Breadcrumb as default };
@@ -1,11 +1,5 @@
1
1
  import type { MouseEvent, ReactNode } from 'react';
2
2
  import React from 'react';
3
- interface Responsive {
4
- minItems: number;
5
- maxItems: number;
6
- minWidth: number;
7
- maxWidth: number;
8
- }
9
3
  interface Props {
10
4
  /**
11
5
  * Unique ID for the component
@@ -50,10 +44,12 @@ interface Props {
50
44
  */
51
45
  className?: string;
52
46
  /**
53
- * Allows to define responsive configuration
54
- * If not provided, visibleItems property will be used
47
+ * Allows for responsive behavior in the carousel.
48
+ * Shows as many items as possible; each item requires a defined width.
49
+ * This overrides the `visibleItems` prop.
50
+ * @default false
55
51
  */
56
- responsive?: Partial<Responsive>;
52
+ responsive?: boolean;
57
53
  /**
58
54
  * Allows to pass a screen reader label for the pagination item next to the current slide number
59
55
  */
@@ -73,12 +69,13 @@ interface Props {
73
69
  */
74
70
  swipeStep?: number;
75
71
  }
76
- declare const SlideItem: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, Required<{
77
- $visibleItems?: Props["visibleItems"];
78
- }> & {
79
- $itemWidthCorrection: number;
72
+ declare const SlideItem: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
80
73
  $isSwiping: boolean;
81
- }>> & string;
74
+ $responsive: boolean;
75
+ $gap: number;
76
+ } & Partial<Required<{
77
+ $visibleItems: Props["visibleItems"];
78
+ }>>>> & string;
82
79
  /** @visibleName Carousel */
83
80
  declare const Carousel: ({ "data-testid": dataTestId, ...props }: Props) => React.JSX.Element;
84
81
  /** @component */
@@ -33,7 +33,6 @@ const SlidesWrapper = styled.div `
33
33
  display: flex;
34
34
  width: 100%;
35
35
  height: 100%;
36
- gap: ${({ $gap }) => $gap}rem;
37
36
  transition-property: transform;
38
37
  transform: translate3d(0px, 0, 0);
39
38
  transition-duration: 0ms;
@@ -44,9 +43,14 @@ const SlideItem = styled.div `
44
43
  display: block;
45
44
  position: relative;
46
45
  flex-shrink: 0;
47
- flex-basis: calc(
48
- ${({ $visibleItems, $itemWidthCorrection }) => `(100% / ${$visibleItems}) - ${$itemWidthCorrection}px`}
49
- );
46
+ padding-right: ${({ $gap }) => $gap}rem;
47
+
48
+ ${({ $responsive, $visibleItems }) => !$responsive && $visibleItems
49
+ ? `flex-basis: calc(100% / ${$visibleItems});`
50
+ : `
51
+ flex-basis: auto;
52
+ width: max-content;
53
+ `}
50
54
 
51
55
  a {
52
56
  pointer-events: ${({ $isSwiping }) => ($isSwiping ? 'none' : 'auto')};
@@ -138,53 +142,51 @@ const Counter = styled.span `
138
142
  `;
139
143
  /** @visibleName Carousel */
140
144
  const Carousel = (_a) => {
141
- var _b;
145
+ var _b, _c;
142
146
  var { 'data-testid': dataTestId } = _a, props = __rest(_a, ['data-testid']);
143
147
  const slidesWrapperRef = useRef(null);
144
148
  const scrollbarRef = useRef(null);
145
149
  const knobRef = useRef(null);
146
- const { isMobile, width } = useWindowSize(theme.breakpoints.md);
150
+ const firstItemRef = useRef(null);
151
+ const { isMobile, width: windowWidth } = useWindowSize(theme.breakpoints.md);
147
152
  const [currentIndex, setCurrentIndex] = useState(0);
148
153
  const [isSwiping, setIsSwiping] = useState(false);
149
- const [calculatedItems, setCalculatedItems] = useState(props.visibleItems || (isMobile ? 1.2 : 1));
154
+ const [autoVisibleItems, setAutoVisibleItems] = useState(null);
150
155
  useEffect(() => {
151
- const calculateVisibleItems = () => {
152
- const defaultValue = props.visibleItems || (isMobile ? 1.2 : 1);
153
- const { minItems, maxItems, minWidth, maxWidth } = props.responsive || {};
154
- if (!width || !minItems || !maxItems || !minWidth || !maxWidth) {
155
- return defaultValue;
156
- }
157
- const calculatedMaxItems = Children.count(props.children) === 1 ? 1 : maxItems;
158
- if (width < minWidth) {
159
- return minItems;
156
+ if (props.responsive) {
157
+ const container = slidesWrapperRef.current;
158
+ const firstItem = firstItemRef.current;
159
+ if (container && firstItem) {
160
+ Array.from(container.children).forEach(itemElement => {
161
+ const item = itemElement;
162
+ item.style.flexBasis = '';
163
+ item.style.width = '';
164
+ });
165
+ const containerWidth = container.offsetWidth;
166
+ const itemWidth = firstItem.offsetWidth;
167
+ if (itemWidth > 0) {
168
+ const realVisibleItems = containerWidth / itemWidth;
169
+ setAutoVisibleItems(Math.max(1, realVisibleItems));
170
+ }
160
171
  }
161
- if (width > maxWidth) {
162
- return calculatedMaxItems;
163
- }
164
- return minItems + ((width - minWidth) / (maxWidth - minWidth)) * (maxItems - minItems);
165
- };
166
- const timeoutId = setTimeout(() => {
167
- setCalculatedItems(calculateVisibleItems());
168
- }, 100);
169
- return () => clearTimeout(timeoutId);
170
- }, [width, isMobile, props.responsive, props.visibleItems, props.children]);
172
+ }
173
+ else {
174
+ setAutoVisibleItems(null);
175
+ }
176
+ }, [props.responsive, windowWidth, props.children]);
171
177
  const getStep = (step, visibleItems) => {
172
178
  if (step > visibleItems) {
173
179
  return Math.floor(visibleItems);
174
180
  }
175
181
  return Math.floor(step);
176
182
  };
177
- const visibleItems = props.visibleItems || calculatedItems;
183
+ const visibleItems = (_b = autoVisibleItems !== null && autoVisibleItems !== void 0 ? autoVisibleItems : props.visibleItems) !== null && _b !== void 0 ? _b : (isMobile ? 1.2 : 1);
178
184
  const slidesWrapperGapSizePx = 20;
179
185
  const slidesCount = Children.count(props.children);
180
186
  const slideScreensCount = Math.max(1, slidesCount - Math.floor(visibleItems) + 1);
181
- const step = getStep((_b = props.swipeStep) !== null && _b !== void 0 ? _b : 1, visibleItems);
187
+ const step = getStep((_c = props.swipeStep) !== null && _c !== void 0 ? _c : 1, visibleItems);
182
188
  const currentStepIndex = Math.ceil(currentIndex / step);
183
189
  const totalSwipeSteps = Math.ceil(slideScreensCount / step + ((slideScreensCount - 1) % step !== 0 ? 1 : 0));
184
- const itemWidthCorrectionRatio = (slidesWrapperGapSizePx * visibleItems) % Math.floor(visibleItems) === 0
185
- ? (visibleItems - 1) / visibleItems
186
- : Math.floor(visibleItems) / visibleItems;
187
- const itemWidthCorrection = itemWidthCorrectionRatio * slidesWrapperGapSizePx;
188
190
  const data = useMemo(() => ({
189
191
  startX: 0,
190
192
  startTime: 0,
@@ -320,12 +322,10 @@ const Carousel = (_a) => {
320
322
  useEffect(() => {
321
323
  if (slidesWrapperRef.current && scrollbarRef.current) {
322
324
  const isRest = Children.count(props.children) - (currentIndex + visibleItems) < 0;
323
- data.itemWidth =
324
- slidesWrapperRef.current.offsetWidth / visibleItems - itemWidthCorrection;
325
+ data.itemWidth = slidesWrapperRef.current.offsetWidth / visibleItems;
325
326
  data.scrollWidth = slidesWrapperRef.current.scrollWidth;
326
327
  data.lastItemX =
327
- (data.itemWidth + slidesWrapperGapSizePx) *
328
- (Children.count(props.children) - visibleItems) -
328
+ data.itemWidth * (Children.count(props.children) - visibleItems) -
329
329
  (isRest ? slidesWrapperGapSizePx * (Math.ceil(visibleItems) - visibleItems) : 0);
330
330
  data.scrollbarToSlidesRatio =
331
331
  data.lastItemX /
@@ -334,14 +334,26 @@ const Carousel = (_a) => {
334
334
  let slidesTransform = 0;
335
335
  if (Children.count(props.children) >= visibleItems) {
336
336
  slidesTransform =
337
- data.itemWidth * currentIndex +
338
- slidesWrapperGapSizePx * currentIndex -
337
+ data.itemWidth * currentIndex -
339
338
  (isRest ? data.itemWidth * (visibleItems % 1) + slidesWrapperGapSizePx : 0);
340
339
  }
341
340
  setElementTransform(slidesWrapperRef, -slidesTransform);
342
341
  setElementTransform(knobRef, slidesTransform / data.scrollbarToSlidesRatio);
343
342
  }
344
- }, [currentIndex, data, itemWidthCorrection, props.children, slideScreensCount, visibleItems]);
343
+ }, [currentIndex, data, props.children, slideScreensCount, visibleItems]);
344
+ useEffect(() => {
345
+ var _a;
346
+ if (props.responsive && autoVisibleItems) {
347
+ const items = (_a = slidesWrapperRef.current) === null || _a === void 0 ? void 0 : _a.children;
348
+ if (items) {
349
+ Array.from(items).forEach(itemElement => {
350
+ const item = itemElement;
351
+ item.style.flexBasis = `calc(100% / ${autoVisibleItems})`;
352
+ item.style.width = '';
353
+ });
354
+ }
355
+ }
356
+ }, [autoVisibleItems, props.responsive]);
345
357
  return (React__default.createElement(CarouselWrapper, { id: props.id, className: props.className, "data-testid": dataTestId },
346
358
  React__default.createElement(Header, { "data-testid": dataTestId && `${dataTestId}-header` },
347
359
  props.title && React__default.createElement(Title, null, props.title),
@@ -353,7 +365,7 @@ const Carousel = (_a) => {
353
365
  React__default.createElement(ButtonArrow, { direction: "left", "aria-label": props.previousAriaLabel, onClick: handleNavigationButtonPreviousClick, disabled: currentIndex <= 0, type: "button" }),
354
366
  React__default.createElement(ButtonArrow, { direction: "right", "aria-label": props.nextAriaLabel, onClick: handleNavigationButtonNextClick, disabled: currentIndex + visibleItems >= Children.count(props.children), type: "button" }))),
355
367
  React__default.createElement(Content, { "data-testid": dataTestId && `${dataTestId}-content` },
356
- React__default.createElement(SlidesWrapper, { ref: slidesWrapperRef, onPointerDown: handleSlidesPointerDown, "$gap": slidesWrapperGapSizePx / 16 }, Children.map(props.children, child => (React__default.createElement(SlideItem, { "$visibleItems": visibleItems, "$itemWidthCorrection": itemWidthCorrection, "$isSwiping": isSwiping, onPointerDown: handlePointerDown }, child))))),
368
+ React__default.createElement(SlidesWrapper, { ref: slidesWrapperRef, onPointerDown: handleSlidesPointerDown }, Children.map(props.children, (child, index) => (React__default.createElement(SlideItem, { ref: index === 0 ? firstItemRef : undefined, "$visibleItems": visibleItems, "$isSwiping": isSwiping, onPointerDown: handlePointerDown, "$responsive": Boolean(props.responsive), "$gap": slidesWrapperGapSizePx / 16 }, child))))),
357
369
  React__default.createElement(Footer, { "data-testid": dataTestId && `${dataTestId}-footer` },
358
370
  React__default.createElement(Pagination, null, [...Array(totalSwipeSteps).keys()].map((value, index) => (React__default.createElement(PaginationItem, { key: value, "aria-label": props.paginationAriaLabel &&
359
371
  `${props.paginationAriaLabel} ${index + 1}`, "aria-current": Math.ceil(currentIndex / step) === index, "$isActive": Math.ceil(currentIndex / step) === index, onClick: handlePaginationItemClick, type: "button" })))),
@@ -251,6 +251,10 @@ const CurrentMonth = styled.div `
251
251
  line-height: ${theme.lineHeight.default};
252
252
  font-weight: ${theme.fontWeight.bold};
253
253
  `;
254
+ /**
255
+ * TODO: Replace the VisuallyHiddenStatus styled component with the global class name.
256
+ * Ticket: https://jira.dna.fi/browse/STYLE-916
257
+ */
254
258
  const VisuallyHiddenStatus = styled.div `
255
259
  position: absolute;
256
260
  left: -9999px;
@@ -34,12 +34,6 @@ interface HeroProps {
34
34
  * Background color when no image is provided
35
35
  */
36
36
  backgroundColor?: string;
37
- /**
38
- * Enable gradient overlay on background
39
- *
40
- * @default false
41
- */
42
- hasGradient?: boolean;
43
37
  /**
44
38
  * Logo image component for logo-style heroes
45
39
  */
@@ -27,9 +27,9 @@ const HeroImage = styled.div `
27
27
  height: ${HERO_CONSTANTS.mobileHeight}px;
28
28
  background-color: ${({ $backgroundColor }) => $backgroundColor || 'transparent'};
29
29
 
30
- ${({ $hasGradient }) => $hasGradient &&
30
+ ${({ $backgroundColor }) => $backgroundColor &&
31
31
  `
32
- linear-gradient(180deg, ${theme.color.background.plum.default}${theme.color.transparency.T0} 0%, ${theme.color.background.plum.default}${theme.color.transparency.T30} 100%);
32
+ background-image: linear-gradient(180deg, ${theme.color.background.plum.default}${theme.color.transparency.T0} 0%, ${theme.color.background.plum.default}${theme.color.transparency.T30} 100%);
33
33
  background-size: 100% 33.33%;
34
34
  background-repeat: no-repeat;
35
35
  background-position: bottom;
@@ -176,7 +176,7 @@ const Hero = (_a) => {
176
176
  var { variant = 'default', headingLevel = 'h1', Image = 'img', LogoImage = 'img', 'data-testid': dataTestId } = _a, props = __rest(_a, ["variant", "headingLevel", "Image", "LogoImage", 'data-testid']);
177
177
  const HeadingTag = headingLevel;
178
178
  return (React__default.createElement(HeroContainer, { "$variant": variant, className: props.className, "data-testid": dataTestId },
179
- React__default.createElement(HeroImage, { "$hasGradient": props.hasGradient, "$backgroundColor": props.backgroundColor },
179
+ React__default.createElement(HeroImage, { "$backgroundColor": props.backgroundColor },
180
180
  props.logoImageProps && (React__default.createElement(LogoImageWrap, null,
181
181
  React__default.createElement(LogoImageContainer, null, renderImage(LogoImage, props.logoImageProps)))),
182
182
  !props.logoImageProps && props.imageProps && renderImage(Image, props.imageProps)),
@@ -18,6 +18,10 @@ const NotificationBadgeElement = styled.div `
18
18
  background-color: ${theme.color.notification.error};
19
19
  border-radius: ${theme.radius.circle};
20
20
  `;
21
+ /**
22
+ * TODO: Replace the VisuallyHidden styled component with the global class name.
23
+ * Ticket: https://jira.dna.fi/browse/STYLE-916
24
+ */
21
25
  const VisuallyHidden = styled.span `
22
26
  position: absolute;
23
27
  width: 1px;
@@ -131,6 +131,10 @@ const DropdownList = styled(CoreULStyles) `
131
131
 
132
132
  ${getElevationShadow({ elevation: 'low' })}
133
133
  `;
134
+ /**
135
+ * TODO: Replace the VisuallyHidden styled component with the global class name.
136
+ * Ticket: https://jira.dna.fi/browse/STYLE-916
137
+ */
134
138
  const VisuallyHidden = styled.span `
135
139
  position: absolute;
136
140
  width: 1px;
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import { type ThemeInterface } from '../../themes/theme';
3
+ interface Props {
4
+ /**
5
+ * Unique ID for the skeleton
6
+ */
7
+ id?: string;
8
+ /**
9
+ * Background color of the skeleton
10
+ *
11
+ * @param {string} sand Uses theme.color.background.sand.E01
12
+ * @param {string} white Uses white background (#FFFFFF)
13
+ *
14
+ * @default 'sand'
15
+ */
16
+ backgroundColor?: 'sand' | 'white';
17
+ /**
18
+ * Opacity level of the skeleton
19
+ *
20
+ * @param {number} 100 100% opacity
21
+ * @param {number} 50 50% opacity
22
+ * @param {number} 25 25% opacity
23
+ *
24
+ * @default 100
25
+ */
26
+ opacity?: 100 | 50 | 25;
27
+ /**
28
+ * Allows to pass in custom radius value from theme
29
+ */
30
+ borderRadius?: keyof ThemeInterface['radius'];
31
+ /**
32
+ * Width of the skeleton
33
+ *
34
+ * @default '100%'
35
+ */
36
+ width?: string;
37
+ /**
38
+ * Height of the skeleton
39
+ *
40
+ * @default '6.25rem'
41
+ */
42
+ height?: string;
43
+ /**
44
+ * Allows to pass testid string for testing purposes
45
+ */
46
+ 'data-testid'?: string;
47
+ /**
48
+ * Allows to pass a custom className
49
+ */
50
+ className?: string;
51
+ /**
52
+ * If used inside a carousel, pass the slide index (starting from 1)
53
+ */
54
+ carouselIndex?: number;
55
+ /**
56
+ * Screen reader label describing the use of the skeleton,
57
+ * e.g., "Loading content" or "Loading image."
58
+ */
59
+ ariaLabel?: string;
60
+ }
61
+ declare const Skeleton: ({ backgroundColor, opacity, width, height, borderRadius, "data-testid": dataTestId, ...props }: Props) => React.JSX.Element;
62
+ /** @component */
63
+ export default Skeleton;
@@ -0,0 +1,65 @@
1
+ import { __rest } from 'tslib';
2
+ import React__default from 'react';
3
+ import { keyframes, styled } from 'styled-components';
4
+ import theme from '../../themes/theme.js';
5
+
6
+ const shimmer = keyframes `
7
+ 100% {
8
+ transform: translateX(100%);
9
+ }
10
+ `;
11
+ const sandRgba = '248, 244, 241';
12
+ const whiteRgba = '255, 255, 255';
13
+ const getAnimationDelay = (carouselIndex) => {
14
+ switch (carouselIndex) {
15
+ case 1:
16
+ return '0s';
17
+ case 2:
18
+ return '0.6s';
19
+ case 3:
20
+ return '1.2s';
21
+ case 4:
22
+ return '1.9s';
23
+ case 5:
24
+ return '2.5s';
25
+ default:
26
+ return '0s';
27
+ }
28
+ };
29
+ const getBackgroundColor = (backgroundColor) => `linear-gradient(90deg,rgba(${backgroundColor}, 0) 0%,rgba(${backgroundColor}, 0.8) 50%,rgba(${backgroundColor}, 0) 100%)`;
30
+ const SkeletonWrapper = styled.div `
31
+ position: relative;
32
+ overflow: hidden;
33
+ background-color: ${({ $backgroundColor }) => $backgroundColor === 'sand'
34
+ ? theme.color.background.sand.E01
35
+ : theme.color.background.white.default};
36
+ opacity: ${({ $opacity }) => ($opacity ? $opacity / 100 : 1)};
37
+ width: ${({ $width }) => $width};
38
+ height: ${({ $height }) => $height};
39
+ border-radius: ${({ $borderRadius }) => theme.radius[$borderRadius || 's']};
40
+
41
+ &::after {
42
+ position: absolute;
43
+ inset: 0;
44
+ transform: translateX(-100%);
45
+ background: ${({ $backgroundColor }) => getBackgroundColor($backgroundColor === 'sand' ? whiteRgba : sandRgba)};
46
+
47
+ animation: ${shimmer} 1.5s infinite;
48
+ content: '';
49
+ }
50
+
51
+ @media (min-width: 600px) {
52
+ &::after {
53
+ animation: ${shimmer} 2.5s infinite;
54
+ animation-delay: ${({ $carouselIndex }) => getAnimationDelay($carouselIndex)};
55
+ }
56
+ }
57
+ `;
58
+ const Skeleton = (_a) => {
59
+ var { backgroundColor = 'sand', opacity = 100, width = '25rem', height = '6.25rem', borderRadius = 's', 'data-testid': dataTestId } = _a, props = __rest(_a, ["backgroundColor", "opacity", "width", "height", "borderRadius", 'data-testid']);
60
+ return (React__default.createElement(React__default.Fragment, null,
61
+ props.ariaLabel && (React__default.createElement("span", { id: props.id, "aria-label": props.ariaLabel, role: "status", "aria-atomic": "true", className: "visually-hidden" })),
62
+ React__default.createElement(SkeletonWrapper, { className: props.className, "data-testid": dataTestId, "aria-hidden": "true", tabIndex: -1, "$backgroundColor": backgroundColor, "$opacity": opacity, "$width": width, "$height": height, "$carouselIndex": props.carouselIndex, "$borderRadius": borderRadius })));
63
+ };
64
+
65
+ export { Skeleton as default };
@@ -56,7 +56,7 @@ const StyledReactTooltip = styled(Tooltip$1) `
56
56
 
57
57
  border: 1px solid ${theme.color.line.L02};
58
58
  padding: ${getMultipliedSize(theme.base.baseWidth, 1.5)};
59
- text-align: center;
59
+ text-align: left;
60
60
  font-size: ${theme.fontSize.default};
61
61
  line-height: ${theme.lineHeight.default};
62
62
  width: max-content;
@@ -2,6 +2,7 @@ export { default as Accordion } from './Accordion/Accordion';
2
2
  export { default as AccordionItem } from './AccordionItem/AccordionItem';
3
3
  export { default as AmountSelector } from './AmountSelector/AmountSelector';
4
4
  export { default as Box } from './Box/Box';
5
+ export { default as Breadcrumb } from './Breadcrumb/Breadcrumb';
5
6
  export { default as Button } from './Button/Button';
6
7
  export { default as ButtonArrow } from './ButtonArrow/ButtonArrow';
7
8
  export { default as ButtonCard } from './ButtonCard/ButtonCard';
@@ -46,6 +47,7 @@ export { default as ReadMore } from './ReadMore/ReadMore';
46
47
  export { default as Search } from './Search/Search';
47
48
  export { default as SecondaryNavigation } from './SecondaryNavigation/SecondaryNavigation';
48
49
  export { default as Selectbox } from './Selectbox/Selectbox';
50
+ export { default as Skeleton } from './Skeleton/Skeleton';
49
51
  export { default as Switch } from './Switch/Switch';
50
52
  export { default as Tab } from './Tab/Tab';
51
53
  export { default as Tabs } from './Tabs/Tabs';
package/build/es/index.js CHANGED
@@ -2,6 +2,7 @@ export { default as Accordion } from './components/Accordion/Accordion.js';
2
2
  export { default as AccordionItem } from './components/AccordionItem/AccordionItem.js';
3
3
  export { default as AmountSelector } from './components/AmountSelector/AmountSelector.js';
4
4
  export { default as Box } from './components/Box/Box.js';
5
+ export { default as Breadcrumb } from './components/Breadcrumb/Breadcrumb.js';
5
6
  export { default as Button } from './components/Button/Button.js';
6
7
  export { default as ButtonArrow } from './components/ButtonArrow/ButtonArrow.js';
7
8
  export { default as ButtonCard } from './components/ButtonCard/ButtonCard.js';
@@ -154,6 +155,7 @@ export { default as ReadMore } from './components/ReadMore/ReadMore.js';
154
155
  export { default as Search } from './components/Search/Search.js';
155
156
  export { default as SecondaryNavigation } from './components/SecondaryNavigation/SecondaryNavigation.js';
156
157
  export { default as Selectbox } from './components/Selectbox/Selectbox.js';
158
+ export { default as Skeleton } from './components/Skeleton/Skeleton.js';
157
159
  export { default as Switch } from './components/Switch/Switch.js';
158
160
  export { default as Tab } from './components/Tab/Tab.js';
159
161
  export { default as Tabs } from './components/Tabs/Tabs.js';
@@ -1,3 +1,4 @@
1
+ import "../assets/fonts/fonts.css";
1
2
  import { media } from '../utils/styledUtils.js';
2
3
  import { css, createGlobalStyle } from 'styled-components';
3
4
  import theme from './theme.js';