@pagopa/io-app-design-system 1.22.0 → 1.23.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.
Files changed (41) hide show
  1. package/lib/commonjs/components/avatar/Avatar.js +21 -8
  2. package/lib/commonjs/components/avatar/Avatar.js.map +1 -1
  3. package/lib/commonjs/components/layout/HeaderSecondLevel.js +2 -0
  4. package/lib/commonjs/components/layout/HeaderSecondLevel.js.map +1 -1
  5. package/lib/commonjs/components/listitems/ListItemHeader.js +2 -1
  6. package/lib/commonjs/components/listitems/ListItemHeader.js.map +1 -1
  7. package/lib/commonjs/components/listitems/ListItemNav.js +13 -3
  8. package/lib/commonjs/components/listitems/ListItemNav.js.map +1 -1
  9. package/lib/commonjs/components/modules/ModuleAttachment.js +196 -0
  10. package/lib/commonjs/components/modules/ModuleAttachment.js.map +1 -0
  11. package/lib/commonjs/components/modules/index.js +11 -0
  12. package/lib/commonjs/components/modules/index.js.map +1 -1
  13. package/lib/module/components/avatar/Avatar.js +22 -8
  14. package/lib/module/components/avatar/Avatar.js.map +1 -1
  15. package/lib/module/components/layout/HeaderSecondLevel.js +2 -0
  16. package/lib/module/components/layout/HeaderSecondLevel.js.map +1 -1
  17. package/lib/module/components/listitems/ListItemHeader.js +2 -1
  18. package/lib/module/components/listitems/ListItemHeader.js.map +1 -1
  19. package/lib/module/components/listitems/ListItemNav.js +13 -3
  20. package/lib/module/components/listitems/ListItemNav.js.map +1 -1
  21. package/lib/module/components/modules/ModuleAttachment.js +186 -0
  22. package/lib/module/components/modules/ModuleAttachment.js.map +1 -0
  23. package/lib/module/components/modules/index.js +1 -0
  24. package/lib/module/components/modules/index.js.map +1 -1
  25. package/lib/typescript/components/avatar/Avatar.d.ts +9 -1
  26. package/lib/typescript/components/avatar/Avatar.d.ts.map +1 -1
  27. package/lib/typescript/components/layout/HeaderSecondLevel.d.ts.map +1 -1
  28. package/lib/typescript/components/listitems/ListItemHeader.d.ts.map +1 -1
  29. package/lib/typescript/components/listitems/ListItemNav.d.ts +2 -1
  30. package/lib/typescript/components/listitems/ListItemNav.d.ts.map +1 -1
  31. package/lib/typescript/components/modules/ModuleAttachment.d.ts +32 -0
  32. package/lib/typescript/components/modules/ModuleAttachment.d.ts.map +1 -0
  33. package/lib/typescript/components/modules/index.d.ts +1 -0
  34. package/lib/typescript/components/modules/index.d.ts.map +1 -1
  35. package/package.json +1 -1
  36. package/src/components/avatar/Avatar.tsx +31 -13
  37. package/src/components/layout/HeaderSecondLevel.tsx +2 -0
  38. package/src/components/listitems/ListItemHeader.tsx +4 -1
  39. package/src/components/listitems/ListItemNav.tsx +15 -3
  40. package/src/components/modules/ModuleAttachment.tsx +254 -0
  41. package/src/components/modules/index.tsx +1 -0
@@ -143,6 +143,7 @@ export const HeaderSecondLevel = ({
143
143
  : "transparent"
144
144
  }));
145
145
 
146
+ const isTitleAccessible = !!title.trim();
146
147
  const titleAnimatedStyle = useAnimatedStyle(() => ({
147
148
  opacity: scrollValues
148
149
  ? interpolate(
@@ -183,6 +184,7 @@ export const HeaderSecondLevel = ({
183
184
  )}
184
185
  <Animated.Text
185
186
  numberOfLines={1}
187
+ accessible={isTitleAccessible}
186
188
  style={[
187
189
  styles.headerTitle,
188
190
  isExperimental
@@ -125,7 +125,10 @@ export const ListItemHeader = ({
125
125
  accessible={endElement === undefined ? true : false}
126
126
  accessibilityLabel={listItemAccessibilityLabel}
127
127
  >
128
- <View style={IOListItemStyles.listItemInner}>
128
+ <View
129
+ style={IOListItemStyles.listItemInner}
130
+ importantForAccessibility={endElement ? "auto" : "no-hide-descendants"}
131
+ >
129
132
  {iconName && (
130
133
  <View style={{ marginRight: iconMargin }}>
131
134
  <Icon
@@ -26,6 +26,7 @@ import { IOIcons, Icon } from "../icons";
26
26
  import { IOLogoPaymentType, LogoPayment } from "../logos";
27
27
  import { HSpacer, VSpacer } from "../spacer";
28
28
  import { Caption, H6, LabelSmall } from "../typography";
29
+ import { LoadingSpinner } from "../loadingSpinner";
29
30
 
30
31
  type ListItemTopElementProps =
31
32
  | {
@@ -40,6 +41,7 @@ type ListItemTopElementProps =
40
41
  type ListItemNavPartialProps = WithTestID<{
41
42
  value: string | React.ReactNode;
42
43
  description?: string | React.ReactNode;
44
+ loading?: boolean;
43
45
  onPress: (event: GestureResponderEvent) => void;
44
46
  // Accessibility
45
47
  accessibilityLabel: string;
@@ -64,7 +66,8 @@ export const ListItemNav = ({
64
66
  accessibilityLabel,
65
67
  testID,
66
68
  hideChevron = false,
67
- topElement
69
+ topElement,
70
+ loading
68
71
  }: ListItemNav) => {
69
72
  const isPressed = useSharedValue(0);
70
73
  const { isExperimental } = useIOExperimentalDesign();
@@ -167,9 +170,17 @@ export const ListItemNav = ({
167
170
  isPressed.value = 0;
168
171
  }, [isPressed]);
169
172
 
173
+ const handleOnPress = (event: GestureResponderEvent) => {
174
+ if (!loading) {
175
+ onPress(event);
176
+ }
177
+ };
178
+
179
+ const primaryColor: IOColors = isExperimental ? "blueIO-500" : "blue";
180
+
170
181
  return (
171
182
  <Pressable
172
- onPress={onPress}
183
+ onPress={handleOnPress}
173
184
  onPressIn={handlePressIn}
174
185
  onPressOut={handlePressOut}
175
186
  accessible={true}
@@ -201,7 +212,8 @@ export const ListItemNav = ({
201
212
  </View>
202
213
  )}
203
214
  <View style={IOStyles.flex}>{listItemNavContent}</View>
204
- {!hideChevron && (
215
+ {loading && <LoadingSpinner color={primaryColor} />}
216
+ {!loading && !hideChevron && (
205
217
  <View style={{ marginLeft: IOListItemVisualParams.iconMargin }}>
206
218
  <Icon
207
219
  name="chevronRightListItem"
@@ -0,0 +1,254 @@
1
+ import React, { useCallback } from "react";
2
+ import {
3
+ GestureResponderEvent,
4
+ Pressable,
5
+ PressableProps,
6
+ StyleSheet,
7
+ View
8
+ } from "react-native";
9
+ import Animated, {
10
+ Extrapolate,
11
+ interpolate,
12
+ useAnimatedStyle,
13
+ useDerivedValue,
14
+ useSharedValue,
15
+ withSpring
16
+ } from "react-native-reanimated";
17
+ import Placeholder from "rn-placeholder";
18
+ import {
19
+ IOColors,
20
+ IOListItemVisualParams,
21
+ IOScaleValues,
22
+ IOSpringValues,
23
+ useIOTheme
24
+ } from "../../core";
25
+ import { WithTestID } from "../../utils/types";
26
+ import { Icon } from "../icons";
27
+ import { LabelSmall } from "../typography";
28
+ import { VSpacer } from "../spacer";
29
+ import { Badge } from "../badge";
30
+ import { LoadingSpinner } from "../loadingSpinner";
31
+
32
+ type PartialProps = WithTestID<{
33
+ title: string;
34
+ format: "doc" | "pdf";
35
+ isLoading?: boolean;
36
+ loadingAccessibilityLabel?: string;
37
+ isFetching?: boolean;
38
+ fetchingAccessibilityLabel?: string;
39
+ onPress: (event: GestureResponderEvent) => void;
40
+ }>;
41
+
42
+ export type ModuleAttachmentProps = PartialProps &
43
+ Pick<
44
+ PressableProps,
45
+ "onPress" | "accessibilityLabel" | "disabled" | "testID"
46
+ >;
47
+
48
+ type SkeletonComponentProps = {
49
+ loadingAccessibilityLabel?: string;
50
+ };
51
+
52
+ const styles = StyleSheet.create({
53
+ button: {
54
+ flexDirection: "row",
55
+ alignItems: "center",
56
+ paddingHorizontal: 16,
57
+ paddingVertical: 16,
58
+ borderRadius: 8,
59
+ borderColor: IOColors.bluegreyLight,
60
+ backgroundColor: IOColors.white,
61
+ borderStyle: "solid",
62
+ borderWidth: 1
63
+ },
64
+ innerContent: {
65
+ flex: 1,
66
+ flexDirection: "column"
67
+ },
68
+ rightSection: {
69
+ marginLeft: IOListItemVisualParams.iconMargin,
70
+ alignItems: "center"
71
+ }
72
+ });
73
+
74
+ const DISABLED_OPACITY = 0.5;
75
+
76
+ const ModuleAttachmentContent = ({
77
+ isFetching,
78
+ format,
79
+ title,
80
+ testID
81
+ }: Pick<
82
+ ModuleAttachmentProps,
83
+ "isFetching" | "format" | "title" | "testID"
84
+ >) => {
85
+ const theme = useIOTheme();
86
+ const IconOrActivityIndicatorComponent = () => {
87
+ if (isFetching) {
88
+ const activityIndicatorTestId = testID
89
+ ? `${testID}_activityIndicator`
90
+ : undefined;
91
+ return <LoadingSpinner testID={activityIndicatorTestId} />;
92
+ }
93
+
94
+ return (
95
+ <Icon
96
+ name="chevronRightListItem"
97
+ color={theme["interactiveElem-default"]}
98
+ size={IOListItemVisualParams.chevronSize}
99
+ />
100
+ );
101
+ };
102
+
103
+ return (
104
+ <>
105
+ <View style={styles.innerContent}>
106
+ <LabelSmall
107
+ numberOfLines={1}
108
+ weight="SemiBold"
109
+ font="ReadexPro"
110
+ color="blueIO-500"
111
+ >
112
+ {title}
113
+ </LabelSmall>
114
+ <VSpacer size={4} />
115
+ <View style={{ width: 44 }}>
116
+ <Badge text={format.toUpperCase()} variant="default" />
117
+ </View>
118
+ </View>
119
+ <View style={styles.rightSection}>
120
+ <IconOrActivityIndicatorComponent />
121
+ </View>
122
+ </>
123
+ );
124
+ };
125
+
126
+ /**
127
+ * The `ModuleAttachment` component is a custom button component with an extended outline style.
128
+ * It provides an animated scaling effect when pressed.
129
+ *
130
+ * @param {string} accessibilityLabel - Optional accessibility label.
131
+ * @param {boolean} disabled - If true, the button is disabled.
132
+ * @param {string} fetchingAccessibilityLabel - Optional accessibility label to use during fetching.
133
+ * @param {string} format - Badge content. PDF or DOC.
134
+ * @param {boolean} isLoading - If true, displays a skeleton loading component.
135
+ * @param {boolean} isFetching - If true, displays an activity indicator.
136
+ * @param {string} loadingAccessibilityLabel - Optional accessibility label to use during loading.
137
+ * @param {function} onPress - The function to be executed when the item is pressed.
138
+ * @param {string} testID - The test ID for testing purposes.
139
+ * @param {string} title - The title text to display.
140
+ *
141
+ */
142
+ export const ModuleAttachment = ({
143
+ accessibilityLabel,
144
+ disabled = false,
145
+ fetchingAccessibilityLabel,
146
+ format,
147
+ isLoading = false,
148
+ isFetching = false,
149
+ loadingAccessibilityLabel,
150
+ onPress,
151
+ testID,
152
+ title
153
+ }: ModuleAttachmentProps) => {
154
+ const isPressed: Animated.SharedValue<number> = useSharedValue(0);
155
+
156
+ // Scaling transformation applied when the button is pressed
157
+ const animationScaleValue = IOScaleValues?.magnifiedButton?.pressedState;
158
+
159
+ const scaleTraversed = useDerivedValue(() =>
160
+ withSpring(isPressed.value, IOSpringValues.button)
161
+ );
162
+
163
+ // Interpolate animation values from `isPressed` values
164
+ const animatedStyle = useAnimatedStyle(() => {
165
+ const scale = interpolate(
166
+ scaleTraversed.value,
167
+ [0, 1],
168
+ [1, animationScaleValue],
169
+ Extrapolate.CLAMP
170
+ );
171
+
172
+ return {
173
+ transform: [{ scale }]
174
+ };
175
+ });
176
+
177
+ const onPressIn = useCallback(() => {
178
+ // eslint-disable-next-line functional/immutable-data
179
+ isPressed.value = 1;
180
+ }, [isPressed]);
181
+
182
+ const onPressOut = useCallback(() => {
183
+ // eslint-disable-next-line functional/immutable-data
184
+ isPressed.value = 0;
185
+ }, [isPressed]);
186
+
187
+ const handleOnPress = useCallback(
188
+ (event: GestureResponderEvent) => {
189
+ if (isFetching) {
190
+ return;
191
+ }
192
+ onPress(event);
193
+ },
194
+ [isFetching, onPress]
195
+ );
196
+
197
+ if (isLoading) {
198
+ return (
199
+ <SkeletonComponent
200
+ loadingAccessibilityLabel={loadingAccessibilityLabel}
201
+ />
202
+ );
203
+ }
204
+
205
+ const pressableAccessibilityLabel =
206
+ (isFetching && !!fetchingAccessibilityLabel
207
+ ? fetchingAccessibilityLabel
208
+ : accessibilityLabel) ?? title;
209
+ return (
210
+ <Pressable
211
+ testID={testID}
212
+ onPress={handleOnPress}
213
+ onPressIn={onPressIn}
214
+ onPressOut={onPressOut}
215
+ accessible={true}
216
+ accessibilityRole={"button"}
217
+ accessibilityHint={format}
218
+ accessibilityLabel={pressableAccessibilityLabel}
219
+ disabled={disabled || isFetching}
220
+ >
221
+ <Animated.View
222
+ style={[
223
+ styles.button,
224
+ animatedStyle,
225
+ { opacity: disabled ? DISABLED_OPACITY : 1 }
226
+ ]}
227
+ accessibilityElementsHidden={true}
228
+ importantForAccessibility="no-hide-descendants"
229
+ >
230
+ <ModuleAttachmentContent
231
+ isFetching={isFetching}
232
+ title={title}
233
+ format={format}
234
+ />
235
+ </Animated.View>
236
+ </Pressable>
237
+ );
238
+ };
239
+
240
+ const SkeletonComponent = ({
241
+ loadingAccessibilityLabel
242
+ }: SkeletonComponentProps) => (
243
+ <View
244
+ style={styles.button}
245
+ accessible={true}
246
+ accessibilityLabel={loadingAccessibilityLabel}
247
+ >
248
+ <View style={styles.innerContent}>
249
+ <Placeholder.Box animate="fade" radius={8} width={107} height={22} />
250
+ <VSpacer size={4} />
251
+ <Placeholder.Box animate="fade" radius={8} width={44} height={22} />
252
+ </View>
253
+ </View>
254
+ );
@@ -2,3 +2,4 @@ export * from "./ModuleIDP";
2
2
  export * from "./ModulePaymentNotice";
3
3
  export * from "./PressableModuleBase";
4
4
  export * from "./ModuleCheckout";
5
+ export * from "./ModuleAttachment";