@lumx/react 2.1.6 → 2.1.9-alpha-thumbnail

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 (57) hide show
  1. package/esm/_internal/DragHandle.js +1 -1
  2. package/esm/_internal/DragHandle.js.map +1 -1
  3. package/esm/_internal/Flag2.js +3 -1
  4. package/esm/_internal/Flag2.js.map +1 -1
  5. package/esm/_internal/List2.js.map +1 -1
  6. package/esm/_internal/Message2.js +2 -2
  7. package/esm/_internal/Message2.js.map +1 -1
  8. package/esm/_internal/Slider2.js +21 -2
  9. package/esm/_internal/Slider2.js.map +1 -1
  10. package/esm/_internal/TextField.js +2 -2
  11. package/esm/_internal/TextField.js.map +1 -1
  12. package/esm/_internal/Thumbnail2.js +63 -764
  13. package/esm/_internal/Thumbnail2.js.map +1 -1
  14. package/esm/_internal/UserBlock.js +1 -0
  15. package/esm/_internal/UserBlock.js.map +1 -1
  16. package/esm/_internal/avatar.js +0 -3
  17. package/esm/_internal/avatar.js.map +1 -1
  18. package/esm/_internal/comment-block.js +0 -3
  19. package/esm/_internal/comment-block.js.map +1 -1
  20. package/esm/_internal/image-block.js +0 -3
  21. package/esm/_internal/image-block.js.map +1 -1
  22. package/esm/_internal/link-preview.js +0 -3
  23. package/esm/_internal/link-preview.js.map +1 -1
  24. package/esm/_internal/mdi.js +2 -2
  25. package/esm/_internal/mdi.js.map +1 -1
  26. package/esm/_internal/mosaic.js +0 -3
  27. package/esm/_internal/mosaic.js.map +1 -1
  28. package/esm/_internal/post-block.js +0 -3
  29. package/esm/_internal/post-block.js.map +1 -1
  30. package/esm/_internal/slider.js +1 -2
  31. package/esm/_internal/slider.js.map +1 -1
  32. package/esm/_internal/thumbnail.js +0 -3
  33. package/esm/_internal/thumbnail.js.map +1 -1
  34. package/esm/_internal/user-block.js +0 -2
  35. package/esm/_internal/user-block.js.map +1 -1
  36. package/esm/index.js +2 -3
  37. package/esm/index.js.map +1 -1
  38. package/package.json +4 -4
  39. package/src/components/drag-handle/DragHandle.tsx +1 -5
  40. package/src/components/flag/Flag.test.tsx +2 -1
  41. package/src/components/flag/Flag.tsx +10 -2
  42. package/src/components/flag/__snapshots__/Flag.test.tsx.snap +15 -0
  43. package/src/components/message/Message.tsx +2 -2
  44. package/src/components/text-field/TextField.stories.tsx +30 -0
  45. package/src/components/text-field/TextField.tsx +11 -9
  46. package/src/components/thumbnail/Thumbnail.stories.tsx +343 -59
  47. package/src/components/thumbnail/Thumbnail.test.tsx +6 -6
  48. package/src/components/thumbnail/Thumbnail.tsx +37 -34
  49. package/src/components/thumbnail/useFocusPoint.ts +18 -10
  50. package/src/components/thumbnail/useImageLoad.ts +23 -22
  51. package/src/components/user-block/UserBlock.stories.tsx +4 -1
  52. package/src/components/user-block/UserBlock.tsx +1 -0
  53. package/src/hooks/useOnResize.ts +6 -0
  54. package/src/stories/knobs/image.ts +35 -3
  55. package/types.d.ts +2 -0
  56. package/esm/_internal/clamp.js +0 -22
  57. package/esm/_internal/clamp.js.map +0 -1
@@ -32,15 +32,19 @@ type Sizes = {
32
32
 
33
33
  function calculateSizes(
34
34
  imageElement?: HTMLImageElement | null | undefined,
35
+ imageWidthProp?: number,
36
+ imageHeightProp?: number,
35
37
  parentElement?: HTMLElement | null,
36
38
  aspectRatio?: AspectRatio,
37
39
  ): Sizes | undefined {
38
- if (!imageElement || !parentElement || !aspectRatio || aspectRatio === AspectRatio.original) return undefined;
39
- const { naturalWidth: imgWidth, naturalHeight: imgHeight } = imageElement || { naturalWidth: 0, naturalHeight: 0 };
40
- const { width: containerWidth, height: containerHeight } = parentElement?.getBoundingClientRect() || {
41
- width: 0,
42
- height: 0,
43
- };
40
+ const imgWidth = imageElement?.naturalWidth || imageWidthProp;
41
+ const imgHeight = imageElement?.naturalHeight || imageHeightProp;
42
+ if (!imgHeight || !imgWidth || !parentElement || !aspectRatio || aspectRatio === AspectRatio.original) {
43
+ return undefined;
44
+ }
45
+ const rect = parentElement?.getBoundingClientRect();
46
+ const containerWidth = Math.ceil(rect?.width || 0);
47
+ const containerHeight = Math.ceil(rect?.height || 0);
44
48
  return { imgWidth, imgHeight, containerWidth, containerHeight, aspectRatio };
45
49
  }
46
50
 
@@ -90,6 +94,7 @@ function calculateImageStyle(sizes: Sizes, point: Required<FocusPoint>): Styles
90
94
  // Minimize image while still filling space
91
95
  if (sizes.imgWidth > sizes.containerWidth && sizes.imgHeight > sizes.containerHeight) {
92
96
  image[widthRatio > heightRatio ? 'maxHeight' : 'maxWidth'] = '100%';
97
+ image[widthRatio > heightRatio ? 'maxWidth' : 'maxHeight'] = 'none';
93
98
  }
94
99
 
95
100
  if (widthRatio > heightRatio) {
@@ -109,13 +114,15 @@ function calculateImageStyle(sizes: Sizes, point: Required<FocusPoint>): Styles
109
114
  */
110
115
  export const useFocusPoint = (options: {
111
116
  image: string;
117
+ imageWidthProp?: number;
118
+ imageHeightProp?: number;
112
119
  focusPoint?: FocusPoint;
113
120
  aspectRatio?: AspectRatio;
114
121
  imgRef: RefObject<HTMLImageElement>;
115
122
  loadingState: LoadingState;
116
123
  wrapper?: HTMLElement;
117
124
  }): Styles | undefined => {
118
- const { image, aspectRatio, focusPoint, imgRef, loadingState, wrapper } = options;
125
+ const { image, imageWidthProp, imageHeightProp, aspectRatio, focusPoint, imgRef, loadingState, wrapper } = options;
119
126
 
120
127
  const point = parseFocusPoint(focusPoint);
121
128
 
@@ -128,12 +135,13 @@ export const useFocusPoint = (options: {
128
135
  const update = useMemo(
129
136
  () => {
130
137
  const updateFunction = () => {
131
- const sizes = calculateSizes(imgRef?.current, wrapper, aspectRatio);
138
+ const sizes = calculateSizes(imgRef?.current, imageWidthProp, imageHeightProp, wrapper, aspectRatio);
132
139
  if (!sizes || (isEqual(sizes, previousSizes.current) && isEqual(point, previousPoint.current))) {
133
140
  // Nothing changed.
134
141
  return;
135
142
  }
136
- setStyle(calculateImageStyle(sizes, point));
143
+ const newStyle = calculateImageStyle(sizes, point);
144
+ setStyle(newStyle);
137
145
  previousPoint.current = point;
138
146
  previousSizes.current = sizes;
139
147
  };
@@ -141,7 +149,7 @@ export const useFocusPoint = (options: {
141
149
  return updateFunction;
142
150
  },
143
151
  // eslint-disable-next-line react-hooks/exhaustive-deps
144
- [...Object.values(point), imgRef, wrapper, aspectRatio],
152
+ [...Object.values(point), wrapper, aspectRatio],
145
153
  );
146
154
 
147
155
  // Update on image loaded.
@@ -1,39 +1,40 @@
1
- import { RefObject, useCallback, useEffect, useState } from 'react';
1
+ import { RefObject, useEffect, useState } from 'react';
2
2
 
3
3
  export type LoadingState = 'isLoading' | 'isLoaded' | 'hasError';
4
4
 
5
- export function useImageLoad(imgRef?: RefObject<HTMLImageElement>): LoadingState {
6
- const [state, setState] = useState<LoadingState>('isLoading');
7
-
8
- const update = useCallback(
9
- (event?: any) => {
10
- const img = imgRef?.current;
11
- if (!img || !img.complete) {
12
- setState('isLoading');
13
- return;
14
- }
5
+ function getState(img: HTMLImageElement | null | undefined, event?: Event) {
6
+ // Error event occurred or image loaded empty.
7
+ if (event?.type === 'error' || (img?.complete && (img?.naturalWidth === 0 || img?.naturalHeight === 0))) {
8
+ return 'hasError';
9
+ }
10
+ // Image is undefined or incomplete.
11
+ if (!img || !img.complete) {
12
+ return 'isLoading';
13
+ }
14
+ // Else loaded.
15
+ return 'isLoaded';
16
+ }
15
17
 
16
- if (event?.type === 'error' || (img.complete && img?.naturalWidth === 0)) {
17
- setState('hasError');
18
- return;
19
- }
18
+ export function useImageLoad(imageURL: string, imgRef?: RefObject<HTMLImageElement>): LoadingState {
19
+ const [state, setState] = useState<LoadingState>(getState(imgRef?.current));
20
20
 
21
- setState('isLoaded');
22
- },
23
- [imgRef],
24
- );
21
+ // Update state when changing image URL or DOM reference.
22
+ useEffect(() => {
23
+ setState(getState(imgRef?.current));
24
+ }, [imageURL, imgRef]);
25
25
 
26
+ // Listen to `load` and `error` event on image
26
27
  useEffect(() => {
27
28
  const img = imgRef?.current;
28
29
  if (!img) return undefined;
29
-
30
- update();
30
+ const update = (event?: Event) => setState(getState(img, event));
31
31
  img.addEventListener('load', update);
32
32
  img.addEventListener('error', update);
33
33
  return () => {
34
34
  img.removeEventListener('load', update);
35
35
  img.removeEventListener('error', update);
36
36
  };
37
- }, [update, imgRef, imgRef?.current?.src]);
37
+ }, [imgRef, imgRef?.current?.src]);
38
+
38
39
  return state;
39
40
  }
@@ -31,7 +31,10 @@ export const WithLinks = ({ theme }: any) => {
31
31
  <UserBlock
32
32
  theme={theme}
33
33
  name="Emmitt O. Lum"
34
- linkProps={{ href: 'https://www.lumapps.com', target: '_blank' }}
34
+ linkProps={{
35
+ href: 'https://www.lumapps.com',
36
+ target: '_blank',
37
+ }}
35
38
  fields={['Creative developer', 'Denpasar']}
36
39
  avatarProps={{ image: avatarImageKnob(), alt: 'Avatar' }}
37
40
  size={size}
@@ -146,6 +146,7 @@ export const UserBlock: Comp<UserBlockProps, HTMLDivElement> = forwardRef((props
146
146
  >
147
147
  {avatarProps && (
148
148
  <Avatar
149
+ linkAs={linkAs}
149
150
  linkProps={linkProps}
150
151
  {...avatarProps}
151
152
  className={classNames(`${CLASSNAME}__avatar`, avatarProps.className)}
@@ -23,6 +23,12 @@ export function useOnResize(element: HTMLElement | Falsy, update: RefObject<Call
23
23
  return;
24
24
  }
25
25
 
26
+ // Do not update on first resize.
27
+ if (previousSize.current && previousSize.current.height <= 1) {
28
+ return;
29
+ }
30
+
31
+ // Do not update if size hasn't really changed.
26
32
  const { width, height } = entry.contentRect;
27
33
  if (previousSize.current?.width === width && previousSize.current?.height === height) {
28
34
  return;
@@ -6,18 +6,19 @@ const avatar3 = '/demo-assets/avatar3.jpg';
6
6
  const avatar4 = '/demo-assets/avatar4.jpg';
7
7
  const landscape1 = '/demo-assets/landscape1.jpg';
8
8
  const landscape2 = '/demo-assets/landscape2.jpg';
9
+ const landscape1s200 = '/demo-assets/landscape1-s200.jpg';
9
10
  const landscape3 = '/demo-assets/landscape3.jpg';
10
11
  const portrait1 = '/demo-assets/portrait1.jpg';
12
+ const portrait1s200 = '/demo-assets/portrait1-s200.jpg';
11
13
  const portrait2 = '/demo-assets/portrait2.jpg';
12
14
  const portrait3 = '/demo-assets/portrait3.jpg';
13
15
  const square1 = '/demo-assets/square1.jpg';
14
16
  const square2 = '/demo-assets/square2.jpg';
15
17
 
16
18
  export const AVATAR_IMAGES = { avatar1, avatar2, avatar3, avatar4 };
17
-
18
19
  export const SQUARE_IMAGES = { square1, square2 };
19
- export const LANDSCAPE_IMAGES = { landscape1, landscape2, landscape3 };
20
- export const PORTRAIT_IMAGES = { portrait1, portrait2, portrait3 };
20
+ export const LANDSCAPE_IMAGES = { landscape1, landscape1s200, landscape2, landscape3 };
21
+ export const PORTRAIT_IMAGES = { portrait1, portrait1s200, portrait2, portrait3 };
21
22
 
22
23
  export const IMAGES = { ...LANDSCAPE_IMAGES, ...PORTRAIT_IMAGES, ...SQUARE_IMAGES, ...AVATAR_IMAGES };
23
24
 
@@ -35,3 +36,34 @@ export const squareImageKnob = (name = 'Image', value = Object.values(SQUARE_IMA
35
36
 
36
37
  export const imageKnob = (name = 'Image', value = Object.values(IMAGES)[0], groupId?: string) =>
37
38
  select(name, IMAGES, value, groupId);
39
+
40
+ type Size = { width: number; height: number };
41
+
42
+ export const AVATAR_IMAGE_SIZES: Record<keyof typeof AVATAR_IMAGES, Size> = {
43
+ avatar1: { width: 128, height: 128 },
44
+ avatar2: { width: 150, height: 150 },
45
+ avatar3: { width: 128, height: 128 },
46
+ avatar4: { width: 128, height: 128 },
47
+ };
48
+ export const SQUARE_IMAGE_SIZES: Record<keyof typeof SQUARE_IMAGES, Size> = {
49
+ square1: { width: 72, height: 72 },
50
+ square2: { width: 300, height: 300 },
51
+ };
52
+ export const LANDSCAPE_IMAGE_SIZES: Record<keyof typeof LANDSCAPE_IMAGES, Size> = {
53
+ landscape1: { width: 800, height: 546 },
54
+ landscape1s200: { width: 200, height: 150 },
55
+ landscape2: { width: 800, height: 600 },
56
+ landscape3: { width: 640, height: 480 },
57
+ };
58
+ export const PORTRAIT_IMAGE_SIZES: Record<keyof typeof PORTRAIT_IMAGES, Size> = {
59
+ portrait1: { width: 275, height: 500 },
60
+ portrait1s200: { width: 200, height: 364 },
61
+ portrait2: { width: 350, height: 500 },
62
+ portrait3: { width: 300, height: 500 },
63
+ };
64
+ export const IMAGE_SIZES: Record<keyof typeof IMAGES, Size> = {
65
+ ...LANDSCAPE_IMAGE_SIZES,
66
+ ...PORTRAIT_IMAGE_SIZES,
67
+ ...SQUARE_IMAGE_SIZES,
68
+ ...AVATAR_IMAGE_SIZES,
69
+ };
package/types.d.ts CHANGED
@@ -1237,6 +1237,8 @@ export interface ThumbnailProps extends GenericProps {
1237
1237
  imgProps?: ImgHTMLProps;
1238
1238
  /** Reference to the native <img> element. */
1239
1239
  imgRef?: Ref<HTMLImageElement>;
1240
+ /** Set to true to force the display of the loading skeleton. */
1241
+ isLoading?: boolean;
1240
1242
  /** Size variant of the component. */
1241
1243
  size?: ThumbnailSize;
1242
1244
  /** Image loading mode. */
@@ -1,22 +0,0 @@
1
- /**
2
- * Clamp value in range.
3
- *
4
- * @param value Value to clamp.
5
- * @param min Minimum value.
6
- * @param max Maximum value.
7
- * @return Clamped value.
8
- */
9
- var clamp = function clamp(value, min, max) {
10
- if (value < min) {
11
- return min;
12
- }
13
-
14
- if (value > max) {
15
- return max;
16
- }
17
-
18
- return value;
19
- };
20
-
21
- export { clamp as c };
22
- //# sourceMappingURL=clamp.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"clamp.js","sources":["../../../src/utils/clamp.ts"],"sourcesContent":["/**\n * Clamp value in range.\n *\n * @param value Value to clamp.\n * @param min Minimum value.\n * @param max Maximum value.\n * @return Clamped value.\n */\nexport const clamp = (value: number, min: number, max: number): number => {\n if (value < min) {\n return min;\n }\n if (value > max) {\n return max;\n }\n return value;\n};\n"],"names":["clamp","value","min","max"],"mappings":"AAAA;;;;;;;;IAQaA,KAAK,GAAG,SAARA,KAAQ,CAACC,KAAD,EAAgBC,GAAhB,EAA6BC,GAA7B,EAAqD;AACtE,MAAIF,KAAK,GAAGC,GAAZ,EAAiB;AACb,WAAOA,GAAP;AACH;;AACD,MAAID,KAAK,GAAGE,GAAZ,EAAiB;AACb,WAAOA,GAAP;AACH;;AACD,SAAOF,KAAP;AACH;;;;"}