@lumx/react 3.8.1-alpha.0 → 3.8.1

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 (31) hide show
  1. package/index.d.ts +6 -63
  2. package/index.js +559 -1292
  3. package/index.js.map +1 -1
  4. package/package.json +3 -3
  5. package/src/components/image-block/ImageBlock.test.tsx +28 -0
  6. package/src/components/image-block/ImageBlock.tsx +5 -1
  7. package/src/components/image-block/ImageCaption.tsx +54 -8
  8. package/src/components/thumbnail/useFocusPointStyle.tsx +4 -3
  9. package/src/index.ts +0 -1
  10. package/src/utils/type.ts +0 -15
  11. package/src/components/image-lightbox/ImageLightbox.stories.tsx +0 -165
  12. package/src/components/image-lightbox/ImageLightbox.test.tsx +0 -253
  13. package/src/components/image-lightbox/ImageLightbox.tsx +0 -72
  14. package/src/components/image-lightbox/constants.ts +0 -11
  15. package/src/components/image-lightbox/index.ts +0 -2
  16. package/src/components/image-lightbox/internal/ImageSlide.tsx +0 -107
  17. package/src/components/image-lightbox/internal/ImageSlideshow.tsx +0 -173
  18. package/src/components/image-lightbox/internal/useAnimateScroll.ts +0 -55
  19. package/src/components/image-lightbox/internal/usePointerZoom.ts +0 -148
  20. package/src/components/image-lightbox/types.ts +0 -50
  21. package/src/components/image-lightbox/useImageLightbox.tsx +0 -130
  22. package/src/hooks/useElementSizeDependentOfWindowSize.ts +0 -32
  23. package/src/hooks/useImageSize.ts +0 -17
  24. package/src/stories/generated/ImageLightbox/Demos.stories.tsx +0 -6
  25. package/src/utils/DOM/findImage.tsx +0 -3
  26. package/src/utils/DOM/startViewTransition.ts +0 -56
  27. package/src/utils/browser/getPrefersReducedMotion.ts +0 -6
  28. package/src/utils/object/isEqual.test.ts +0 -25
  29. package/src/utils/object/isEqual.ts +0 -11
  30. package/src/utils/react/unref.ts +0 -7
  31. package/src/utils/unref.ts +0 -0
package/package.json CHANGED
@@ -6,8 +6,8 @@
6
6
  "url": "https://github.com/lumapps/design-system/issues"
7
7
  },
8
8
  "dependencies": {
9
- "@lumx/core": "^3.8.1-alpha.0",
10
- "@lumx/icons": "^3.8.1-alpha.0",
9
+ "@lumx/core": "^3.8.1",
10
+ "@lumx/icons": "^3.8.1",
11
11
  "@popperjs/core": "^2.5.4",
12
12
  "body-scroll-lock": "^3.1.5",
13
13
  "classnames": "^2.3.2",
@@ -111,5 +111,5 @@
111
111
  "build:storybook": "storybook build"
112
112
  },
113
113
  "sideEffects": false,
114
- "version": "3.8.1-alpha.0"
114
+ "version": "3.8.1"
115
115
  }
@@ -3,7 +3,10 @@ import { commonTestsSuiteRTL } from '@lumx/react/testing/utils';
3
3
 
4
4
  import { render } from '@testing-library/react';
5
5
  import { queryByClassName } from '@lumx/react/testing/utils/queries';
6
+ import { toNestedProps } from '@lumx/react/stories/decorators/withNestedProps';
7
+
6
8
  import { ImageBlock, ImageBlockProps } from './ImageBlock';
9
+ import { FullFeatured } from './ImageBlock.stories';
7
10
 
8
11
  const CLASSNAME = ImageBlock.className as string;
9
12
 
@@ -14,6 +17,31 @@ const setup = (props: Partial<ImageBlockProps> = {}) => {
14
17
  };
15
18
 
16
19
  describe(`<${ImageBlock.displayName}>`, () => {
20
+ it('should render caption elements', () => {
21
+ const props = {
22
+ ...toNestedProps(FullFeatured.args),
23
+ titleProps: { className: 'custom-title-class' },
24
+ descriptionProps: { className: 'custom-description-class' },
25
+ };
26
+ const { imageBlock } = setup(props);
27
+ const wrapper = queryByClassName(imageBlock as HTMLElement, 'lumx-image-block__wrapper');
28
+ expect(wrapper).toBeInTheDocument();
29
+
30
+ const caption = queryByClassName(wrapper as HTMLElement, 'lumx-image-block__caption');
31
+ expect(caption).toBeInTheDocument();
32
+
33
+ const title = queryByClassName(caption as HTMLElement, 'lumx-image-block__title');
34
+ expect(title).toBeInTheDocument();
35
+ expect(title).toHaveClass(props.titleProps.className);
36
+
37
+ const description = queryByClassName(caption as HTMLElement, 'lumx-image-block__description');
38
+ expect(description).toBeInTheDocument();
39
+ expect(description).toHaveClass(props.descriptionProps.className);
40
+
41
+ const tags = queryByClassName(wrapper as HTMLElement, 'lumx-image-block__tags');
42
+ expect(tags).toBeInTheDocument();
43
+ });
44
+
17
45
  // Common tests suite.
18
46
  commonTestsSuiteRTL(setup, {
19
47
  baseClassName: CLASSNAME,
@@ -81,6 +81,7 @@ export const ImageBlock: Comp<ImageBlockProps, HTMLDivElement> = forwardRef((pro
81
81
  captionStyle,
82
82
  className,
83
83
  description,
84
+ descriptionProps,
84
85
  fillHeight,
85
86
  image,
86
87
  size,
@@ -88,6 +89,7 @@ export const ImageBlock: Comp<ImageBlockProps, HTMLDivElement> = forwardRef((pro
88
89
  theme,
89
90
  thumbnailProps,
90
91
  title,
92
+ titleProps,
91
93
  ...forwardedProps
92
94
  } = props;
93
95
  return (
@@ -118,10 +120,12 @@ export const ImageBlock: Comp<ImageBlockProps, HTMLDivElement> = forwardRef((pro
118
120
  />
119
121
  <ImageCaption
120
122
  as="figcaption"
121
- className={`${CLASSNAME}__wrapper`}
123
+ baseClassName={CLASSNAME}
122
124
  theme={theme}
123
125
  title={title}
126
+ titleProps={titleProps}
124
127
  description={description}
128
+ descriptionProps={descriptionProps}
125
129
  tags={tags}
126
130
  captionStyle={captionStyle}
127
131
  align={align}
@@ -1,15 +1,22 @@
1
1
  import React, { CSSProperties, ReactNode } from 'react';
2
2
 
3
3
  import { FlexBox, HorizontalAlignment, Text, TextProps } from '@lumx/react';
4
- import { HasClassName, HasPolymorphicAs, HasTheme } from '@lumx/react/utils/type';
4
+ import { HasPolymorphicAs, HasTheme } from '@lumx/react/utils/type';
5
+ import classNames from 'classnames';
5
6
 
6
7
  type As = 'div' | 'figcaption';
7
8
 
9
+ type ForwardedTextProps = Omit<TextProps, 'as' | 'typography' | 'color' | 'colorVariant'>;
10
+
8
11
  export type ImageCaptionMetadata = {
9
12
  /** Image title to display in the caption. */
10
13
  title?: string;
14
+ /** Props to pass to the title. */
15
+ titleProps?: ForwardedTextProps;
11
16
  /** Image description. Can be either a string, or sanitized html. */
12
17
  description?: string | { __html: string };
18
+ /** Props to pass to the title. */
19
+ descriptionProps?: ForwardedTextProps;
13
20
  /** Tag content. */
14
21
  tags?: ReactNode;
15
22
  /** Caption custom CSS style. */
@@ -17,9 +24,10 @@ export type ImageCaptionMetadata = {
17
24
  };
18
25
 
19
26
  export type ImageCaptionProps<AS extends As = 'figcaption'> = HasTheme &
20
- HasClassName &
21
27
  HasPolymorphicAs<AS> &
22
28
  ImageCaptionMetadata & {
29
+ /** Base className for sub elements */
30
+ baseClassName?: string;
23
31
  /** Alignment. */
24
32
  align?: HorizontalAlignment;
25
33
  /** Truncate text on title & description (no line wrapping). */
@@ -28,7 +36,19 @@ export type ImageCaptionProps<AS extends As = 'figcaption'> = HasTheme &
28
36
 
29
37
  /** Internal component used to render image captions */
30
38
  export const ImageCaption = <AS extends As>(props: ImageCaptionProps<AS>) => {
31
- const { className, theme, as = 'figcaption', title, description, tags, captionStyle, align, truncate } = props;
39
+ const {
40
+ baseClassName,
41
+ theme,
42
+ as = 'figcaption',
43
+ title,
44
+ titleProps,
45
+ description,
46
+ descriptionProps,
47
+ tags,
48
+ captionStyle,
49
+ align,
50
+ truncate,
51
+ } = props;
32
52
  if (!title && !description && !tags) return null;
33
53
 
34
54
  const titleColor = { color: theme === 'dark' ? 'light' : 'dark' } as const;
@@ -41,7 +61,7 @@ export const ImageCaption = <AS extends As>(props: ImageCaptionProps<AS>) => {
41
61
  return (
42
62
  <FlexBox
43
63
  as={as}
44
- className={className}
64
+ className={classNames(baseClassName && `${baseClassName}__wrapper`)}
45
65
  style={captionStyle}
46
66
  orientation="vertical"
47
67
  vAlign={align}
@@ -49,17 +69,43 @@ export const ImageCaption = <AS extends As>(props: ImageCaptionProps<AS>) => {
49
69
  gap="regular"
50
70
  >
51
71
  {(title || description) && (
52
- <Text as="p" truncate={truncate} {...baseColor}>
72
+ <Text
73
+ as="p"
74
+ className={classNames(baseClassName && `${baseClassName}__caption`)}
75
+ truncate={truncate}
76
+ {...baseColor}
77
+ >
53
78
  {title && (
54
- <Text as="span" typography="subtitle1" {...titleColor}>
79
+ <Text
80
+ {...titleProps}
81
+ as="span"
82
+ className={classNames(titleProps?.className, baseClassName && `${baseClassName}__title`)}
83
+ typography="subtitle1"
84
+ {...titleColor}
85
+ >
55
86
  {title}
56
87
  </Text>
57
88
  )}{' '}
58
- {description && <Text as="span" typography="body1" {...descriptionContent} />}
89
+ {description && (
90
+ <Text
91
+ {...descriptionProps}
92
+ as="span"
93
+ className={classNames(
94
+ descriptionProps?.className,
95
+ baseClassName && `${baseClassName}__description`,
96
+ )}
97
+ typography="body1"
98
+ {...descriptionContent}
99
+ />
100
+ )}
59
101
  </Text>
60
102
  )}
61
103
  {tags && (
62
- <FlexBox orientation="horizontal" vAlign={align}>
104
+ <FlexBox
105
+ className={classNames(baseClassName && `${baseClassName}__tags`)}
106
+ orientation="horizontal"
107
+ vAlign={align}
108
+ >
63
109
  {tags}
64
110
  </FlexBox>
65
111
  )}
@@ -1,7 +1,6 @@
1
1
  import { CSSProperties, useEffect, useMemo, useState } from 'react';
2
2
  import { AspectRatio } from '@lumx/react/components';
3
3
  import { ThumbnailProps } from '@lumx/react/components/thumbnail/Thumbnail';
4
- import { RectSize } from '@lumx/react/utils/type';
5
4
 
6
5
  // Calculate shift to center the focus point in the container.
7
6
  export function shiftPosition({
@@ -25,6 +24,8 @@ export function shiftPosition({
25
24
  return Math.floor(Math.max(Math.min(shift, 1), 0) * 100);
26
25
  }
27
26
 
27
+ type Size = { width: number; height: number };
28
+
28
29
  // Compute CSS properties to apply the focus point.
29
30
  export const useFocusPointStyle = (
30
31
  { image, aspectRatio, focusPoint, imgProps: { width, height } = {} }: ThumbnailProps,
@@ -32,7 +33,7 @@ export const useFocusPointStyle = (
32
33
  isLoaded: boolean,
33
34
  ): CSSProperties => {
34
35
  // Get natural image size from imgProps or img element.
35
- const imageSize: RectSize | undefined = useMemo(() => {
36
+ const imageSize: Size | undefined = useMemo(() => {
36
37
  // Focus point is not applicable => exit early
37
38
  if (!image || aspectRatio === AspectRatio.original || (!focusPoint?.x && !focusPoint?.y)) return undefined;
38
39
  if (typeof width === 'number' && typeof height === 'number') return { width, height };
@@ -41,7 +42,7 @@ export const useFocusPointStyle = (
41
42
  }, [aspectRatio, element, focusPoint?.x, focusPoint?.y, height, image, isLoaded, width]);
42
43
 
43
44
  // Get container size (dependant on imageSize).
44
- const [containerSize, setContainerSize] = useState<RectSize | undefined>(undefined);
45
+ const [containerSize, setContainerSize] = useState<Size | undefined>(undefined);
45
46
  useEffect(
46
47
  function updateContainerSize() {
47
48
  const cWidth = element?.offsetWidth;
package/src/index.ts CHANGED
@@ -25,7 +25,6 @@ export * from './components/grid';
25
25
  export * from './components/grid-column';
26
26
  export * from './components/icon';
27
27
  export * from './components/image-block';
28
- export * from './components/image-lightbox';
29
28
  export * from './components/inline-list';
30
29
  export * from './components/input-helper';
31
30
  export * from './components/input-label';
package/src/utils/type.ts CHANGED
@@ -139,18 +139,3 @@ export type ComponentRef<C> = C extends keyof JSX.IntrinsicElements
139
139
  : C extends React.JSXElementConstructor<{ ref?: infer R }>
140
140
  ? R
141
141
  : never;
142
-
143
- /**
144
- * Rectangle size
145
- */
146
- export type RectSize = { width: number; height: number };
147
-
148
- /**
149
- * Maybe a HTMLElement or a React ref of a HTMLElement
150
- */
151
- export type MaybeElementOrRef<E extends HTMLElement> = E | React.RefObject<E | null> | null | undefined;
152
-
153
- /**
154
- * A point coordinate in 2D space
155
- */
156
- export type Point = { x: number; y: number };
@@ -1,165 +0,0 @@
1
- import React from 'react';
2
- import { IMAGES } from '@lumx/react/stories/controls/image';
3
- import { Button, Mosaic, ImageLightbox, ImageLightboxProps, Chip, ChipGroup } from '@lumx/react';
4
- import { withWrapper } from '@lumx/react/stories/decorators/withWrapper';
5
-
6
- const ZOOM_PROPS = {
7
- zoomInButtonProps: { label: 'Zoom in' },
8
- zoomOutButtonProps: { label: 'Zoom out' },
9
- };
10
-
11
- const SLIDESHOW_PROPS = {
12
- slideshowControlsProps: {
13
- nextButtonProps: { label: 'Next' },
14
- previousButtonProps: { label: 'Previous' },
15
- paginationItemProps: (index: number) => ({ label: `Go to slide ${index + 1}` }),
16
- },
17
- };
18
-
19
- const MULTIPLE_IMAGES: ImageLightboxProps['images'] = [
20
- {
21
- image: 'https://picsum.photos/id/237/2000/3000',
22
- alt: 'Image 1',
23
- title: 'Little puppy',
24
- description: 'A black labrador puppy with big brown eyes, looking up with a curious and innocent expression.',
25
- tags: (
26
- <ChipGroup>
27
- <Chip theme="dark" size="s">
28
- Tag 1
29
- </Chip>
30
- <Chip theme="dark" size="s">
31
- Tag 2
32
- </Chip>
33
- </ChipGroup>
34
- ),
35
- },
36
- {
37
- image: 'https://picsum.photos/id/337/3000/1000',
38
- alt: 'Image 2',
39
- // Intentionally using the wrong size to see how it renders while loading the image
40
- imgProps: { width: '300px', height: '100px' },
41
- },
42
- {
43
- image: 'https://picsum.photos/id/437/2000/2000',
44
- alt: 'Image 3',
45
- },
46
- {
47
- image: 'https://picsum.photos/id/537/300/400',
48
- alt: 'Image 4',
49
- },
50
- {
51
- image: 'https://picsum.photos/id/637/400/200',
52
- alt: 'Image 5',
53
- },
54
- {
55
- image: 'https://picsum.photos/id/737/300/300',
56
- alt: 'Image 6',
57
- },
58
- ];
59
-
60
- export default {
61
- title: 'LumX components/image-lightbox/ImageLightbox',
62
- component: ImageLightbox,
63
- args: {
64
- closeButtonProps: { label: 'Close' },
65
- },
66
- argTypes: {
67
- onClose: { action: true },
68
- },
69
- };
70
-
71
- /**
72
- * Display a single image fullscreen in the ImageLightbox
73
- */
74
- export const SingleImage = {
75
- args: {
76
- images: [{ image: IMAGES.portrait1s200, alt: 'Image 1' }],
77
- isOpen: true,
78
- },
79
- };
80
-
81
- /**
82
- * Display a single image fullscreen in the ImageLightbox with zoom controls
83
- */
84
- export const SingleImageWithZoom = {
85
- ...SingleImage,
86
- args: { ...SingleImage.args, ...ZOOM_PROPS },
87
- };
88
-
89
- /**
90
- * Display a single image fullscreen in the ImageLightbox with metadata (title, description, etc.)
91
- */
92
- export const SingleImageWithMetadata = {
93
- ...SingleImage,
94
- args: { ...SingleImage.args, images: [MULTIPLE_IMAGES[0]] },
95
- };
96
-
97
- /**
98
- * Display a multiple image fullscreen in the ImageLightbox
99
- */
100
- export const MultipleImages = {
101
- args: {
102
- images: MULTIPLE_IMAGES,
103
- isOpen: true,
104
- ...SLIDESHOW_PROPS,
105
- },
106
- };
107
-
108
- /**
109
- * Display a multiple images fullscreen in the ImageLightbox with zoom controls
110
- */
111
- export const MultipleImagesWithZoom = {
112
- ...MultipleImages,
113
- args: { ...MultipleImages.args, ...ZOOM_PROPS },
114
- };
115
-
116
- /**
117
- * Open ImageLightbox via buttons
118
- */
119
- export const WithButtonTrigger = {
120
- decorators: [
121
- (Story: any, { args }: any) => {
122
- const { getTriggerProps, imageLightboxProps } = ImageLightbox.useImageLightbox({
123
- images: [
124
- { image: IMAGES.portrait1s200, alt: 'Image 1' },
125
- { image: IMAGES.landscape1s200, alt: 'Image 2' },
126
- ],
127
- });
128
- return (
129
- <>
130
- <Story args={{ ...args, ...imageLightboxProps }} />
131
- <Button {...(getTriggerProps({ activeImageIndex: 0 }) as any)}>Image 1</Button>
132
- <Button {...(getTriggerProps({ activeImageIndex: 1 }) as any)}>Image 2</Button>
133
- </>
134
- );
135
- },
136
- ],
137
- // Disables Chromatic snapshot (not relevant for this story).
138
- parameters: { chromatic: { disable: true } },
139
- };
140
-
141
- /**
142
- * Open ImageLightbox with zoom and slideshow via clickable thumbnails in a Mosaic
143
- */
144
- export const WithMosaicTrigger = {
145
- args: { ...SLIDESHOW_PROPS, ...ZOOM_PROPS },
146
- decorators: [
147
- (Story: any, { args }: any) => {
148
- const { getTriggerProps, imageLightboxProps } = ImageLightbox.useImageLightbox({ images: MULTIPLE_IMAGES });
149
- return (
150
- <>
151
- <Story args={{ ...args, ...imageLightboxProps }} />
152
- <Mosaic
153
- thumbnails={MULTIPLE_IMAGES.map((image, index) => ({
154
- ...image,
155
- ...getTriggerProps({ activeImageIndex: index }),
156
- }))}
157
- />
158
- </>
159
- );
160
- },
161
- withWrapper({ style: { width: 300 } }),
162
- ],
163
- // Disables Chromatic snapshot (not relevant for this story).
164
- parameters: { chromatic: { disable: true } },
165
- };