@artsy/palette-mobile 19.16.0 → 19.18.0--canary.403.4734.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/dist/elements/Avatar/Avatar.d.ts +2 -1
- package/dist/elements/Avatar/Avatar.js +2 -2
- package/dist/elements/Button/Button.js +2 -4
- package/dist/elements/Button/Button.stories.d.ts +15 -1
- package/dist/elements/Button/Button.stories.js +18 -0
- package/dist/elements/Dialog/Dialog.tests.js +15 -1
- package/dist/elements/Image/Image.js +3 -12
- package/dist/elements/Image/__tests__/getImageURL.tests.d.ts +1 -0
- package/dist/elements/Image/__tests__/getImageURL.tests.js +30 -0
- package/dist/elements/Image/helpers/getImageURL.d.ts +14 -0
- package/dist/elements/Image/helpers/getImageURL.js +25 -0
- package/dist/utils/createGeminiUrl.d.ts +1 -0
- package/dist/utils/createGeminiUrl.js +4 -1
- package/package.json +2 -3
|
@@ -7,6 +7,7 @@ export interface AvatarProps extends ImgHTMLAttributes<any> {
|
|
|
7
7
|
/** The size of the Avatar */
|
|
8
8
|
size?: AvatarSize;
|
|
9
9
|
src?: string;
|
|
10
|
+
performResize?: boolean;
|
|
10
11
|
}
|
|
11
12
|
/** A circular Avatar component containing an image or initials */
|
|
12
|
-
export declare const Avatar: ({ src, initials, size, blurhash }: AvatarProps) => import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export declare const Avatar: ({ src, initials, size, blurhash, performResize, }: AvatarProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -26,11 +26,11 @@ const VARIANTS = {
|
|
|
26
26
|
},
|
|
27
27
|
};
|
|
28
28
|
/** A circular Avatar component containing an image or initials */
|
|
29
|
-
export const Avatar = ({ src, initials, size = DEFAULT_SIZE, blurhash }) => {
|
|
29
|
+
export const Avatar = ({ src, initials, size = DEFAULT_SIZE, blurhash, performResize = true, }) => {
|
|
30
30
|
const color = useColor();
|
|
31
31
|
const { diameter, textSize } = VARIANTS[size];
|
|
32
32
|
if (src) {
|
|
33
|
-
return (_jsx(MotiView, { entering: FadeIn, children: _jsx(Box, { width: diameter, height: diameter, borderRadius: diameter / 2, overflow: "hidden", borderColor: color("mono0"), borderWidth: 1, children: _jsx(Image, { blurhash: blurhash, resizeMode: "cover", src: src, height: diameter, width: diameter, accessibilityLabel: "AvatarImage", style: {
|
|
33
|
+
return (_jsx(MotiView, { entering: FadeIn, children: _jsx(Box, { width: diameter, height: diameter, borderRadius: diameter / 2, overflow: "hidden", borderColor: color("mono0"), borderWidth: 1, children: _jsx(Image, { blurhash: blurhash, resizeMode: "cover", src: src, height: diameter, width: diameter, accessibilityLabel: "AvatarImage", performResize: performResize, style: {
|
|
34
34
|
width: diameter,
|
|
35
35
|
height: diameter,
|
|
36
36
|
} }) }) }));
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { animated, Spring, config } from "@react-spring/native";
|
|
2
3
|
import { useState } from "react";
|
|
3
4
|
import { Pressable } from "react-native";
|
|
4
5
|
import Haptic from "react-native-haptic-feedback";
|
|
5
|
-
import { config } from "react-spring";
|
|
6
|
-
// @ts-ignore
|
|
7
|
-
import { animated, Spring } from "react-spring/renderprops-native";
|
|
8
6
|
import styled from "styled-components/native";
|
|
9
7
|
import { useColor } from "../../utils/hooks";
|
|
10
8
|
import { isTestEnvironment } from "../../utils/tests/isTestEnvironment";
|
|
@@ -69,7 +67,7 @@ export const Button = ({ children, disabled, haptic, icon, iconPosition = "left"
|
|
|
69
67
|
};
|
|
70
68
|
const containerSize = getSize();
|
|
71
69
|
const to = useStyleForVariantAndState(variant, testOnly_state ?? displayState);
|
|
72
|
-
return (_jsx(Spring, {
|
|
70
|
+
return (_jsx(Spring, { to: to, config: config.stiff, children: (springProps) => (_jsx(Pressable, { accessibilityLabel: rest.accessibilityLabel, accessibilityRole: "button", accessibilityState: {
|
|
73
71
|
disabled,
|
|
74
72
|
}, hitSlop: hitSlop, testOnly_pressed: testOnly_state === DisplayState.Pressed, disabled: testOnly_state === DisplayState.Disabled || disabled, onPressIn: () => {
|
|
75
73
|
if (displayState === DisplayState.Loading) {
|
|
@@ -1,9 +1,23 @@
|
|
|
1
|
-
import { ButtonProps } from "./Button";
|
|
1
|
+
import { Button, ButtonProps } from "./Button";
|
|
2
|
+
import { NoUndefined } from "../../utils/types";
|
|
3
|
+
import type { StoryObj } from "@storybook/react";
|
|
2
4
|
declare const _default: {
|
|
3
5
|
title: string;
|
|
4
6
|
component: import("react").FC<ButtonProps>;
|
|
7
|
+
argTypes: {
|
|
8
|
+
variant: {
|
|
9
|
+
control: string;
|
|
10
|
+
options: NoUndefined<"text" | "outline" | "fillDark" | "fillLight" | "fillGray" | "fillSuccess" | "outlineGray" | "outlineLight">[];
|
|
11
|
+
};
|
|
12
|
+
size: {
|
|
13
|
+
control: string;
|
|
14
|
+
options: NoUndefined<"small" | "large">[];
|
|
15
|
+
};
|
|
16
|
+
};
|
|
5
17
|
};
|
|
6
18
|
export default _default;
|
|
19
|
+
type ButtonStory = StoryObj<typeof Button>;
|
|
20
|
+
export declare const Default: ButtonStory;
|
|
7
21
|
export declare const Sizes: () => import("react/jsx-runtime").JSX.Element;
|
|
8
22
|
export declare const States: () => import("react/jsx-runtime").JSX.Element;
|
|
9
23
|
export declare const Variants: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -20,6 +20,24 @@ const variants = [
|
|
|
20
20
|
export default {
|
|
21
21
|
title: "Button",
|
|
22
22
|
component: Button,
|
|
23
|
+
argTypes: {
|
|
24
|
+
variant: {
|
|
25
|
+
control: "select",
|
|
26
|
+
options: variants,
|
|
27
|
+
},
|
|
28
|
+
size: {
|
|
29
|
+
control: "select",
|
|
30
|
+
options: sizes,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
export const Default = {
|
|
35
|
+
args: {
|
|
36
|
+
variant: "fillDark",
|
|
37
|
+
size: "large",
|
|
38
|
+
children: "Press me",
|
|
39
|
+
},
|
|
40
|
+
render: (args) => _jsx(Button, { ...args }),
|
|
23
41
|
};
|
|
24
42
|
export const Sizes = () => (_jsx(DataList, { data: sizes, renderItem: ({ item: size }) => (_jsx(Button, { size: size, onPress: () => console.log(`tapped ${size}`), children: capitalize(size) })) }));
|
|
25
43
|
export const States = () => {
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { fireEvent, screen } from "@testing-library/react-native";
|
|
2
|
+
import { fireEvent, screen, act } from "@testing-library/react-native";
|
|
3
3
|
import { Dialog } from "./Dialog";
|
|
4
4
|
import { renderWithWrappers } from "../../utils/tests/renderWithWrappers";
|
|
5
5
|
describe("Dialog", () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
jest.useFakeTimers();
|
|
8
|
+
});
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
jest.runOnlyPendingTimers();
|
|
11
|
+
jest.useRealTimers();
|
|
12
|
+
});
|
|
6
13
|
it("renders without error", () => {
|
|
7
14
|
renderWithWrappers(_jsx(Dialog, { title: "title", isVisible: true, primaryCta: {
|
|
8
15
|
text: "Primary Action Button",
|
|
@@ -25,6 +32,9 @@ describe("Dialog", () => {
|
|
|
25
32
|
} }));
|
|
26
33
|
const primaryButton = screen.getByTestId("dialog-primary-action-button");
|
|
27
34
|
fireEvent.press(primaryButton);
|
|
35
|
+
act(() => {
|
|
36
|
+
jest.runAllTimers();
|
|
37
|
+
});
|
|
28
38
|
expect(primaryActionMock).toHaveBeenCalled();
|
|
29
39
|
expect(screen.getByText("Primary Action Button")).toBeOnTheScreen();
|
|
30
40
|
});
|
|
@@ -39,6 +49,7 @@ describe("Dialog", () => {
|
|
|
39
49
|
} }));
|
|
40
50
|
const secondaryButton = screen.getByTestId("dialog-secondary-action-button");
|
|
41
51
|
fireEvent.press(secondaryButton);
|
|
52
|
+
jest.runAllTimers();
|
|
42
53
|
expect(secondaryActionMock).toHaveBeenCalled();
|
|
43
54
|
expect(screen.getByText("Secondary Action Button")).toBeOnTheScreen();
|
|
44
55
|
});
|
|
@@ -49,6 +60,9 @@ describe("Dialog", () => {
|
|
|
49
60
|
onPress: jest.fn(),
|
|
50
61
|
} }));
|
|
51
62
|
fireEvent.press(screen.getByTestId("dialog-backdrop"));
|
|
63
|
+
act(() => {
|
|
64
|
+
jest.runAllTimers();
|
|
65
|
+
});
|
|
52
66
|
expect(onBackgroundPressMock).toHaveBeenCalled();
|
|
53
67
|
});
|
|
54
68
|
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import FastImage from "@d11/react-native-fast-image";
|
|
3
3
|
import { memo, useRef } from "react";
|
|
4
|
-
import {
|
|
4
|
+
import { View, Animated } from "react-native";
|
|
5
5
|
import { Blurhash } from "react-native-blurhash";
|
|
6
|
-
import {
|
|
6
|
+
import { getImageURL } from "./helpers/getImageURL";
|
|
7
7
|
import { useColor } from "../../utils/hooks";
|
|
8
8
|
import { useScreenDimensions } from "../../utils/hooks/useScreenDimensions";
|
|
9
9
|
import { Flex } from "../Flex";
|
|
@@ -22,15 +22,6 @@ export const Image = memo(({ aspectRatio, width, height, performResize = true, s
|
|
|
22
22
|
if (showLoadingState) {
|
|
23
23
|
return (_jsx(ImageSkeleton, { dimensions: dimensions, blurhash: blurhash, style: { position: "absolute" } }));
|
|
24
24
|
}
|
|
25
|
-
let uri = src;
|
|
26
|
-
if (performResize) {
|
|
27
|
-
uri = createGeminiUrl({
|
|
28
|
-
imageURL: src,
|
|
29
|
-
width: PixelRatio.getPixelSizeForLayoutSize(dimensions.width),
|
|
30
|
-
height: PixelRatio.getPixelSizeForLayoutSize(dimensions.height),
|
|
31
|
-
resizeMode: geminiResizeMode,
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
25
|
return (_jsxs(Flex, { position: "relative", ...flexProps, style: { ...dimensions }, children: [_jsx(View, { style: [dimensions, { position: "absolute" }], children: _jsx(ImageSkeleton, { dimensions: dimensions, blurhash: blurhash, style: { position: "absolute" } }) }), _jsx(FastImage, { style: [
|
|
35
26
|
dimensions,
|
|
36
27
|
style,
|
|
@@ -39,7 +30,7 @@ export const Image = memo(({ aspectRatio, width, height, performResize = true, s
|
|
|
39
30
|
{ backgroundColor: blurhash ? "transparent" : color("mono30") },
|
|
40
31
|
], resizeMode: resizeMode, onLoadEnd: onLoadEnd, source: {
|
|
41
32
|
priority: FastImage.priority.normal,
|
|
42
|
-
uri,
|
|
33
|
+
uri: getImageURL({ src, dimensions, geminiResizeMode, performResize }),
|
|
43
34
|
} })] }));
|
|
44
35
|
});
|
|
45
36
|
const useImageDimensions = (props) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getImageURL } from "../helpers/getImageURL";
|
|
2
|
+
describe("getImageURL", () => {
|
|
3
|
+
it("should return the original url if performResize is true", () => {
|
|
4
|
+
const url = getImageURL({
|
|
5
|
+
src: "https://example.com/image.jpg",
|
|
6
|
+
dimensions: { width: 100, height: 100 },
|
|
7
|
+
geminiResizeMode: "fill",
|
|
8
|
+
performResize: true,
|
|
9
|
+
});
|
|
10
|
+
expect(url).toBe("https://d7hftxdivxxvm.cloudfront.net/?height=200&quality=80&resize_to=fill&src=https%3A%2F%2Fexample.com%2Fimage.jpg&width=200");
|
|
11
|
+
});
|
|
12
|
+
it("should return the original url if performResize is true and the url is already resized", () => {
|
|
13
|
+
const url = getImageURL({
|
|
14
|
+
src: "https://d7hftxdivxxvm.cloudfront.net/?height=100&quality=80&resize_to=fill&src=https%3A%2F%2Fexample.com%2Fimage.jpg&width=100",
|
|
15
|
+
dimensions: { width: 100, height: 100 },
|
|
16
|
+
geminiResizeMode: "fill",
|
|
17
|
+
performResize: true,
|
|
18
|
+
});
|
|
19
|
+
expect(url).toBe("https://d7hftxdivxxvm.cloudfront.net/?height=100&quality=80&resize_to=fill&src=https%3A%2F%2Fexample.com%2Fimage.jpg&width=100");
|
|
20
|
+
});
|
|
21
|
+
it("should return the original url if performResize is false", () => {
|
|
22
|
+
const url = getImageURL({
|
|
23
|
+
src: "https://example.com/image.jpg",
|
|
24
|
+
dimensions: { width: 100, height: 100 },
|
|
25
|
+
geminiResizeMode: "fill",
|
|
26
|
+
performResize: false,
|
|
27
|
+
});
|
|
28
|
+
expect(url).toBe("https://example.com/image.jpg");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { GeminiResizeMode } from "../../../utils/createGeminiUrl";
|
|
2
|
+
/**
|
|
3
|
+
* This method retuns a valid image url to be used within an Image Component
|
|
4
|
+
* It handles the case of resizing the image on the fly using Gemini
|
|
5
|
+
*/
|
|
6
|
+
export declare const getImageURL: ({ src, dimensions, geminiResizeMode, performResize, }: {
|
|
7
|
+
src: string;
|
|
8
|
+
dimensions: {
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
};
|
|
12
|
+
geminiResizeMode: GeminiResizeMode;
|
|
13
|
+
performResize: boolean;
|
|
14
|
+
}) => string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { PixelRatio } from "react-native";
|
|
2
|
+
import { createGeminiUrl, imageAlreadyResized, } from "../../../utils/createGeminiUrl";
|
|
3
|
+
/**
|
|
4
|
+
* This method retuns a valid image url to be used within an Image Component
|
|
5
|
+
* It handles the case of resizing the image on the fly using Gemini
|
|
6
|
+
*/
|
|
7
|
+
export const getImageURL = ({ src, dimensions, geminiResizeMode, performResize, }) => {
|
|
8
|
+
let uri = src;
|
|
9
|
+
if (performResize) {
|
|
10
|
+
if (!imageAlreadyResized(src)) {
|
|
11
|
+
uri = createGeminiUrl({
|
|
12
|
+
imageURL: src,
|
|
13
|
+
width: PixelRatio.getPixelSizeForLayoutSize(dimensions.width),
|
|
14
|
+
height: PixelRatio.getPixelSizeForLayoutSize(dimensions.height),
|
|
15
|
+
resizeMode: geminiResizeMode,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
if (__DEV__) {
|
|
20
|
+
console.warn("You are resizing a gemini url that is already resized. Pass performResize={false} or do not use a resized url");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return uri;
|
|
25
|
+
};
|
|
@@ -3,8 +3,11 @@ const geminiHosts = [
|
|
|
3
3
|
"https://d7hftxdivxxvm.cloudfront.net",
|
|
4
4
|
"https://d196wkiy8qx2u5.cloudfront.net",
|
|
5
5
|
];
|
|
6
|
+
export const imageAlreadyResized = (imageURL) => {
|
|
7
|
+
return geminiHosts.some((host) => imageURL.includes(host));
|
|
8
|
+
};
|
|
6
9
|
export function createGeminiUrl({ imageURL, width, height, geminiHost = "d7hftxdivxxvm.cloudfront.net", imageQuality = 80, resizeMode = "fill", }) {
|
|
7
|
-
if (
|
|
10
|
+
if (imageAlreadyResized(imageURL)) {
|
|
8
11
|
console.error("Image: `performResize` on self referential url. Avoid resizing gemini urls. Pass performResize={false} to fix this.", { imageURL });
|
|
9
12
|
}
|
|
10
13
|
const src = encodeURIComponent(imageURL);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@artsy/palette-mobile",
|
|
3
|
-
"version": "19.
|
|
3
|
+
"version": "19.18.0--canary.403.4734.0",
|
|
4
4
|
"description": "Artsy's design system for React Native",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"android": "expo run:android --port 8082",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"@artsy/icons": "^3.49.0",
|
|
38
38
|
"@artsy/palette-tokens": "7.0.0",
|
|
39
39
|
"@d11/react-native-fast-image": "8.12.0",
|
|
40
|
+
"@react-spring/native": "^10.0.3",
|
|
40
41
|
"@shopify/flash-list": "^1.8.3",
|
|
41
42
|
"@styled-system/core": "^5.1.2",
|
|
42
43
|
"@styled-system/theme-get": "^5.1.2",
|
|
@@ -48,7 +49,6 @@
|
|
|
48
49
|
"react-native-collapsible-tab-view": "^8.0.1",
|
|
49
50
|
"react-native-pager-view": "6.7.1",
|
|
50
51
|
"react-native-popover-view": "^6.1.0",
|
|
51
|
-
"react-spring": "8.0.23",
|
|
52
52
|
"styled-system": "^5.1.5"
|
|
53
53
|
},
|
|
54
54
|
"peerDependenciesComments": [
|
|
@@ -149,7 +149,6 @@
|
|
|
149
149
|
"files": [
|
|
150
150
|
"dist"
|
|
151
151
|
],
|
|
152
|
-
"main": "dist/index.js",
|
|
153
152
|
"types": "dist/index.d.ts",
|
|
154
153
|
"publishConfig": {
|
|
155
154
|
"access": "public"
|