react-native-terra-ui 0.9.1 → 0.10.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.
- package/CHANGELOG.md +23 -0
- package/lib/module/components/avatar/Avatar.js +7 -2
- package/lib/module/components/avatar/Avatar.js.map +1 -1
- package/lib/module/components/header/header-buttons.js +1 -1
- package/lib/module/components/header/header-buttons.js.map +1 -1
- package/lib/module/components/icon/Icon.js +2 -2
- package/lib/module/components/toast/Toast.js +1 -1
- package/lib/module/components/toast/Toast.js.map +1 -1
- package/lib/module/components/toast/ToastProvider.js +9 -236
- package/lib/module/components/toast/ToastProvider.js.map +1 -1
- package/lib/module/components/toast/hooks/use-toast-manager.js +96 -0
- package/lib/module/components/toast/hooks/use-toast-manager.js.map +1 -0
- package/lib/module/components/toast/parts/ToastAnimatedItem.js +34 -50
- package/lib/module/components/toast/parts/ToastAnimatedItem.js.map +1 -1
- package/lib/module/components/toast/parts/ToastOverlay.ios.js +36 -0
- package/lib/module/components/toast/parts/ToastOverlay.ios.js.map +1 -0
- package/lib/module/components/toast/parts/ToastOverlay.js +34 -0
- package/lib/module/components/toast/parts/ToastOverlay.js.map +1 -0
- package/lib/module/components/toast/parts/ToastViewport.js +91 -0
- package/lib/module/components/toast/parts/ToastViewport.js.map +1 -0
- package/lib/module/theme/defaults/icons.js +26 -19
- package/lib/module/theme/defaults/icons.js.map +1 -1
- package/lib/typescript/src/components/avatar/Avatar.d.ts.map +1 -1
- package/lib/typescript/src/components/icon/Icon.d.ts +2 -2
- package/lib/typescript/src/components/toast/ToastProvider.d.ts.map +1 -1
- package/lib/typescript/src/components/toast/hooks/use-toast-manager.d.ts +6 -0
- package/lib/typescript/src/components/toast/hooks/use-toast-manager.d.ts.map +1 -0
- package/lib/typescript/src/components/toast/parts/ToastAnimatedItem.d.ts +3 -2
- package/lib/typescript/src/components/toast/parts/ToastAnimatedItem.d.ts.map +1 -1
- package/lib/typescript/src/components/toast/parts/ToastOverlay.d.ts +28 -0
- package/lib/typescript/src/components/toast/parts/ToastOverlay.d.ts.map +1 -0
- package/lib/typescript/src/components/toast/parts/ToastOverlay.ios.d.ts +28 -0
- package/lib/typescript/src/components/toast/parts/ToastOverlay.ios.d.ts.map +1 -0
- package/lib/typescript/src/components/toast/parts/ToastViewport.d.ts +11 -0
- package/lib/typescript/src/components/toast/parts/ToastViewport.d.ts.map +1 -0
- package/lib/typescript/src/theme/defaults/icons.d.ts.map +1 -1
- package/lib/typescript/src/theme/types.d.ts +1 -1
- package/lib/typescript/src/theme/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/avatar/Avatar.tsx +6 -1
- package/src/components/header/header-buttons.tsx +1 -1
- package/src/components/icon/Icon.tsx +2 -2
- package/src/components/toast/Toast.tsx +1 -1
- package/src/components/toast/ToastProvider.tsx +7 -288
- package/src/components/toast/hooks/use-toast-manager.tsx +114 -0
- package/src/components/toast/parts/ToastAnimatedItem.tsx +32 -50
- package/src/components/toast/parts/ToastOverlay.ios.tsx +30 -0
- package/src/components/toast/parts/ToastOverlay.tsx +28 -0
- package/src/components/toast/parts/ToastViewport.tsx +104 -0
- package/src/theme/defaults/icons.tsx +22 -15
- package/src/theme/types.ts +6 -5
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import type { ReactElement } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { View } from "react-native";
|
|
3
3
|
|
|
4
4
|
import Animated, {
|
|
5
5
|
Easing,
|
|
6
|
-
Extrapolation,
|
|
7
|
-
interpolate,
|
|
8
6
|
Keyframe,
|
|
9
7
|
type SharedValue,
|
|
10
8
|
useAnimatedStyle,
|
|
11
9
|
withSpring,
|
|
12
10
|
withTiming,
|
|
13
11
|
} from "react-native-reanimated";
|
|
12
|
+
import { StyleSheet } from "react-native-unistyles";
|
|
14
13
|
|
|
15
14
|
import type { ToastId, ToastPlacement } from "../types";
|
|
16
15
|
|
|
@@ -21,7 +20,8 @@ export interface ToastAnimatedItemProps {
|
|
|
21
20
|
index: number;
|
|
22
21
|
maxVisibleToasts: number;
|
|
23
22
|
placement: ToastPlacement;
|
|
24
|
-
total:
|
|
23
|
+
total: number;
|
|
24
|
+
frontId: ToastId;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export const enteringTop = new Keyframe({
|
|
@@ -72,12 +72,7 @@ export const exitingBottom = new Keyframe({
|
|
|
72
72
|
},
|
|
73
73
|
}).duration(150);
|
|
74
74
|
|
|
75
|
-
const
|
|
76
|
-
const opacityTimingConfig = { duration: 300 };
|
|
77
|
-
const translateYValue = [0, 10] as [number, number];
|
|
78
|
-
const translateYTimingConfig = { duration: 300 };
|
|
79
|
-
const scaleValue = [1, 0.97] as [number, number];
|
|
80
|
-
const scaleTimingConfig = { duration: 300 };
|
|
75
|
+
const timingConfig = { duration: 300 };
|
|
81
76
|
|
|
82
77
|
export function ToastAnimatedItem({
|
|
83
78
|
children,
|
|
@@ -87,28 +82,14 @@ export function ToastAnimatedItem({
|
|
|
87
82
|
maxVisibleToasts,
|
|
88
83
|
placement,
|
|
89
84
|
total,
|
|
85
|
+
frontId,
|
|
90
86
|
}: ToastAnimatedItemProps) {
|
|
87
|
+
const depth = total - index - 1;
|
|
88
|
+
const isVisible = depth < maxVisibleToasts;
|
|
91
89
|
const containerStyle = useAnimatedStyle(() => {
|
|
92
|
-
const
|
|
93
|
-
const lastToastId = heightEntries[heightEntries.length - 1]?.[0];
|
|
94
|
-
const lastToastHeight = lastToastId ? heights.value[lastToastId] : undefined;
|
|
95
|
-
const totalValue = total.value;
|
|
90
|
+
const lastToastHeight = heights.value[frontId];
|
|
96
91
|
const sign = placement === "top" ? 1 : -1;
|
|
97
92
|
|
|
98
|
-
const inputRange = [totalValue - 1, totalValue - 2];
|
|
99
|
-
const opacityInputRange = [totalValue - maxVisibleToasts, totalValue - maxVisibleToasts - 1];
|
|
100
|
-
|
|
101
|
-
const opacity = interpolate(index, opacityInputRange, opacityValue);
|
|
102
|
-
const translateY = interpolate(
|
|
103
|
-
index,
|
|
104
|
-
inputRange,
|
|
105
|
-
[translateYValue[0], translateYValue[1] * sign],
|
|
106
|
-
Extrapolation.CLAMP
|
|
107
|
-
);
|
|
108
|
-
const scale = interpolate(index, inputRange, scaleValue, {
|
|
109
|
-
extrapolateLeft: Extrapolation.CLAMP,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
93
|
return {
|
|
113
94
|
height: lastToastHeight
|
|
114
95
|
? withSpring(lastToastHeight, {
|
|
@@ -117,11 +98,10 @@ export function ToastAnimatedItem({
|
|
|
117
98
|
mass: 3,
|
|
118
99
|
})
|
|
119
100
|
: undefined,
|
|
120
|
-
|
|
121
|
-
opacity: withTiming(opacity, opacityTimingConfig),
|
|
101
|
+
opacity: withTiming(isVisible ? 1 : 0, timingConfig),
|
|
122
102
|
transform: [
|
|
123
|
-
{ translateY: withTiming(
|
|
124
|
-
{ scale: withTiming(
|
|
103
|
+
{ translateY: withTiming(Math.min(depth, 1) * 10 * sign, timingConfig) },
|
|
104
|
+
{ scale: withTiming(Math.max(0, 1 - depth * 0.03), timingConfig) },
|
|
125
105
|
],
|
|
126
106
|
zIndex: 1000 + index,
|
|
127
107
|
};
|
|
@@ -134,23 +114,26 @@ export function ToastAnimatedItem({
|
|
|
134
114
|
pointerEvents="box-none"
|
|
135
115
|
style={[styles.position, placement === "top" ? styles.top : styles.bottom]}
|
|
136
116
|
>
|
|
137
|
-
<Animated.View
|
|
138
|
-
|
|
139
|
-
pointerEvents="none"
|
|
140
|
-
|
|
141
|
-
importantForAccessibility="no-hide-descendants"
|
|
142
|
-
|
|
143
|
-
style={styles.measurement}
|
|
144
|
-
onLayout={(event) => {
|
|
145
|
-
const measuredHeight = event.nativeEvent.layout.height;
|
|
146
|
-
heights.value = {
|
|
147
|
-
...heights.value,
|
|
148
|
-
[id]: measuredHeight,
|
|
149
|
-
};
|
|
150
|
-
}}
|
|
117
|
+
<Animated.View
|
|
118
|
+
style={containerStyle}
|
|
119
|
+
pointerEvents={isVisible ? "box-none" : "none"}
|
|
120
|
+
accessibilityElementsHidden={!isVisible}
|
|
121
|
+
importantForAccessibility={isVisible ? "auto" : "no-hide-descendants"}
|
|
122
|
+
aria-hidden={!isVisible}
|
|
151
123
|
>
|
|
152
|
-
{
|
|
153
|
-
|
|
124
|
+
{/* An unconstrained child measures natural height without mounting a second copy. */}
|
|
125
|
+
<View
|
|
126
|
+
style={[styles.content, placement === "top" ? styles.top : styles.bottom]}
|
|
127
|
+
onLayout={(event) => {
|
|
128
|
+
const height = event.nativeEvent.layout.height;
|
|
129
|
+
if (heights.value[id] !== height) {
|
|
130
|
+
heights.value = { ...heights.value, [id]: height };
|
|
131
|
+
}
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
{children}
|
|
135
|
+
</View>
|
|
136
|
+
</Animated.View>
|
|
154
137
|
</Animated.View>
|
|
155
138
|
);
|
|
156
139
|
}
|
|
@@ -167,10 +150,9 @@ const styles = StyleSheet.create({
|
|
|
167
150
|
bottom: {
|
|
168
151
|
bottom: 0,
|
|
169
152
|
},
|
|
170
|
-
|
|
153
|
+
content: {
|
|
171
154
|
position: "absolute",
|
|
172
155
|
left: 0,
|
|
173
156
|
right: 0,
|
|
174
|
-
opacity: 0,
|
|
175
157
|
},
|
|
176
158
|
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import { FullWindowOverlay } from "react-native-screens";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lifts the toast viewport into a window of its own, above every presented
|
|
7
|
+
* view controller.
|
|
8
|
+
*
|
|
9
|
+
* Without this, a toast raised from a screen presented as `modal` or
|
|
10
|
+
* `formSheet` is invisible. `ToastProvider` sits above the app's navigator, so
|
|
11
|
+
* its viewport belongs to the root view controller's view; UIKit presents a
|
|
12
|
+
* sheet as a *separate* view controller on top of that one. They are different
|
|
13
|
+
* presentation layers, so no `zIndex` or `elevation` can lift the viewport over
|
|
14
|
+
* the sheet — the toast is mounted and animating, just underneath. The reader
|
|
15
|
+
* presses something, the app reports the result, and nothing appears.
|
|
16
|
+
*
|
|
17
|
+
* `FullWindowOverlay` hosts its children in its own `UIWindow`, which sits
|
|
18
|
+
* above presented controllers, so the viewport paints over a sheet the same way
|
|
19
|
+
* it paints over an ordinary screen.
|
|
20
|
+
*
|
|
21
|
+
* It covers the whole window, but it does not swallow the app's touches:
|
|
22
|
+
* `RNSFullWindowOverlay`'s `pointInside:` walks its subviews and answers NO
|
|
23
|
+
* anywhere they are not, so presses fall through to whatever is underneath.
|
|
24
|
+
*
|
|
25
|
+
* Mounted only alongside a non-empty viewport — `ToastViewport` returns `null`
|
|
26
|
+
* before reaching this — so an idle app carries no extra window.
|
|
27
|
+
*/
|
|
28
|
+
export function ToastOverlay({ children }: { children: ReactNode }) {
|
|
29
|
+
return <FullWindowOverlay>{children}</FullWindowOverlay>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hosts the toast viewport where it already is, because on Android it is
|
|
5
|
+
* already in the right place.
|
|
6
|
+
*
|
|
7
|
+
* The iOS variant explains the problem: a sheet there is a separate view
|
|
8
|
+
* controller, so a toast in the root tree paints underneath it. Android does
|
|
9
|
+
* not have that problem for these screens. `react-native-screens` handles
|
|
10
|
+
* `formSheet` as a `StackPresentation` inside the screen stack (see
|
|
11
|
+
* `Screen.kt`), which lives in the Activity's own window — the same window as
|
|
12
|
+
* this provider — so the viewport's `elevation: 9999` puts it on top the
|
|
13
|
+
* ordinary way. Verified on an API 36 emulator: a toast raised from a
|
|
14
|
+
* `formSheet` route renders over that sheet.
|
|
15
|
+
*
|
|
16
|
+
* Not to be confused with `ScreenModalFragment`, which *is* a
|
|
17
|
+
* `BottomSheetDialogFragment` and so does carry its own `Window`. It backs a
|
|
18
|
+
* different presentation, not the `formSheet` route above. A toast raised from
|
|
19
|
+
* something hosted in a real `Dialog` — that fragment, or React Native's own
|
|
20
|
+
* `Modal` — would still be behind it, and `elevation` cannot cross windows.
|
|
21
|
+
* Untested, and left alone: `FullWindowOverlay` is iOS-only (elsewhere it warns
|
|
22
|
+
* and renders a plain `View`), so the remedy there would be to own a window
|
|
23
|
+
* too, via a React Native `Modal`, which brings its own back-button and
|
|
24
|
+
* touch-capture behaviour.
|
|
25
|
+
*/
|
|
26
|
+
export function ToastOverlay({ children }: { children: ReactNode }) {
|
|
27
|
+
return <>{children}</>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { Keyboard, Platform } from "react-native";
|
|
3
|
+
|
|
4
|
+
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
|
|
5
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
6
|
+
|
|
7
|
+
import type { ToastId, ToastItem, ToastManager, ToastPlacement, ToastProviderProps } from "../types";
|
|
8
|
+
import { ToastAnimatedItem } from "./ToastAnimatedItem";
|
|
9
|
+
import { ToastOverlay } from "./ToastOverlay";
|
|
10
|
+
|
|
11
|
+
interface ToastViewportProps {
|
|
12
|
+
placement: ToastPlacement;
|
|
13
|
+
toasts: ToastItem[];
|
|
14
|
+
manager: ToastManager;
|
|
15
|
+
maxVisibleToasts: number;
|
|
16
|
+
offset?: ToastProviderProps["offset"];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function ToastViewport({ placement, toasts, manager, maxVisibleToasts, offset }: ToastViewportProps) {
|
|
20
|
+
const safeAreaInsets = useSafeAreaInsets();
|
|
21
|
+
const heights = useSharedValue<Record<ToastId, number>>({});
|
|
22
|
+
const keyboardHeight = useSharedValue(0);
|
|
23
|
+
|
|
24
|
+
const baseBottom = offset?.bottom ?? safeAreaInsets.bottom + (Platform.OS === "ios" ? 6 : 12);
|
|
25
|
+
const baseTop = offset?.top ?? safeAreaInsets.top + (Platform.OS === "ios" ? 0 : 12);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (placement !== "bottom") return;
|
|
29
|
+
const showEvent = Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow";
|
|
30
|
+
const hideEvent = Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide";
|
|
31
|
+
const showSub = Keyboard.addListener(showEvent, (e) => {
|
|
32
|
+
keyboardHeight.value = withTiming(e.endCoordinates.height);
|
|
33
|
+
});
|
|
34
|
+
const hideSub = Keyboard.addListener(hideEvent, () => {
|
|
35
|
+
keyboardHeight.value = withTiming(0);
|
|
36
|
+
});
|
|
37
|
+
return () => {
|
|
38
|
+
showSub.remove();
|
|
39
|
+
hideSub.remove();
|
|
40
|
+
};
|
|
41
|
+
}, [keyboardHeight, placement]);
|
|
42
|
+
|
|
43
|
+
const gap = Platform.OS === "ios" ? 6 : 12;
|
|
44
|
+
|
|
45
|
+
const keyboardStyle = useAnimatedStyle(() => {
|
|
46
|
+
if (placement === "top") return { top: baseTop };
|
|
47
|
+
if (keyboardHeight.value > 0) {
|
|
48
|
+
return { bottom: keyboardHeight.value + gap };
|
|
49
|
+
}
|
|
50
|
+
return { bottom: baseBottom };
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const activeIds = new Set(toasts.map((toast) => toast.id));
|
|
55
|
+
const nextHeights: Record<ToastId, number> = {};
|
|
56
|
+
for (const [id, height] of Object.entries(heights.value)) {
|
|
57
|
+
if (activeIds.has(id)) {
|
|
58
|
+
nextHeights[id] = height;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
heights.value = nextHeights;
|
|
62
|
+
}, [heights, toasts]);
|
|
63
|
+
|
|
64
|
+
const frontToast = toasts[toasts.length - 1];
|
|
65
|
+
if (!frontToast) return null;
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<ToastOverlay>
|
|
69
|
+
<Animated.View
|
|
70
|
+
pointerEvents="box-none"
|
|
71
|
+
style={[
|
|
72
|
+
{
|
|
73
|
+
position: "absolute",
|
|
74
|
+
zIndex: 9999,
|
|
75
|
+
elevation: 9999,
|
|
76
|
+
left: offset?.left ?? safeAreaInsets.left + 12,
|
|
77
|
+
right: offset?.right ?? safeAreaInsets.right + 12,
|
|
78
|
+
},
|
|
79
|
+
keyboardStyle,
|
|
80
|
+
]}
|
|
81
|
+
>
|
|
82
|
+
{toasts.map((toast, index) => (
|
|
83
|
+
<ToastAnimatedItem
|
|
84
|
+
key={toast.id}
|
|
85
|
+
heights={heights}
|
|
86
|
+
id={toast.id}
|
|
87
|
+
index={index}
|
|
88
|
+
maxVisibleToasts={maxVisibleToasts}
|
|
89
|
+
placement={placement}
|
|
90
|
+
total={toasts.length}
|
|
91
|
+
frontId={frontToast.id}
|
|
92
|
+
>
|
|
93
|
+
{toast.component({
|
|
94
|
+
id: toast.id,
|
|
95
|
+
index,
|
|
96
|
+
hide: manager.hide,
|
|
97
|
+
show: manager.show,
|
|
98
|
+
})}
|
|
99
|
+
</ToastAnimatedItem>
|
|
100
|
+
))}
|
|
101
|
+
</Animated.View>
|
|
102
|
+
</ToastOverlay>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -19,6 +19,11 @@ const strokeProps = (color: string, strokeWidth: number) => ({
|
|
|
19
19
|
strokeLinejoin: "round" as const,
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
+
// Every registry icon draws through this: one 24×24 viewBox rendered at exactly
|
|
23
|
+
// the requested `size`, so `<Icon size={24} />` is the same optical weight
|
|
24
|
+
// whichever name it resolves to. A glyph that needs to sit smaller than its box
|
|
25
|
+
// (Avatar's person fallback) asks for a smaller `size` — it must not shrink
|
|
26
|
+
// itself here, or it would scale differently from every other icon.
|
|
22
27
|
const SvgIcon = ({ size, children }: { size: number; children: React.ReactNode }) => (
|
|
23
28
|
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
24
29
|
{children}
|
|
@@ -36,25 +41,25 @@ const PathIcon = ({ d, ...props }: TerraIconProps & { d: string }) => {
|
|
|
36
41
|
};
|
|
37
42
|
|
|
38
43
|
// Lucide chevron geometry scaled 20% from the 24×24 viewBox center (45° arms unchanged).
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const
|
|
44
|
+
const CHEVRON_CENTER = 12;
|
|
45
|
+
const CHEVRON_OPEN = 3 * 1.2;
|
|
46
|
+
const CHEVRON_ARM = 6 * 1.2;
|
|
42
47
|
|
|
43
|
-
const
|
|
48
|
+
const ChevronLeftIcon: TerraIconComponent = (props) => (
|
|
44
49
|
<PathIcon
|
|
45
|
-
d={`M${
|
|
50
|
+
d={`M${CHEVRON_CENTER + CHEVRON_OPEN} ${CHEVRON_CENTER + CHEVRON_ARM}L${CHEVRON_CENTER - CHEVRON_OPEN} ${CHEVRON_CENTER} ${CHEVRON_CENTER + CHEVRON_OPEN} ${CHEVRON_CENTER - CHEVRON_ARM}`}
|
|
46
51
|
{...props}
|
|
47
52
|
/>
|
|
48
53
|
);
|
|
49
54
|
|
|
50
|
-
const
|
|
55
|
+
const ChevronRightIcon: TerraIconComponent = (props) => (
|
|
51
56
|
<PathIcon
|
|
52
|
-
d={`M${
|
|
57
|
+
d={`M${CHEVRON_CENTER - CHEVRON_OPEN} ${CHEVRON_CENTER + CHEVRON_ARM}L${CHEVRON_CENTER + CHEVRON_OPEN} ${CHEVRON_CENTER} ${CHEVRON_CENTER - CHEVRON_OPEN} ${CHEVRON_CENTER - CHEVRON_ARM}`}
|
|
53
58
|
{...props}
|
|
54
59
|
/>
|
|
55
60
|
);
|
|
56
61
|
|
|
57
|
-
const
|
|
62
|
+
const XIcon: TerraIconComponent = (props) => <PathIcon d="M18 6L6 18M6 6l12 12" {...props} />;
|
|
58
63
|
|
|
59
64
|
const InfoIcon: TerraIconComponent = (props) => {
|
|
60
65
|
const { color, size, strokeWidth } = resolveIconProps(props);
|
|
@@ -107,26 +112,28 @@ const DangerIcon: TerraIconComponent = (props) => {
|
|
|
107
112
|
);
|
|
108
113
|
};
|
|
109
114
|
|
|
115
|
+
const CheckIcon: TerraIconComponent = (props) => <PathIcon d="M20 6 9 17l-5-5" {...props} />;
|
|
116
|
+
|
|
110
117
|
export const PersonIcon: TerraIconComponent = (props) => {
|
|
111
118
|
const { color, size, strokeWidth } = resolveIconProps(props);
|
|
112
119
|
const stroke = strokeProps(color, strokeWidth);
|
|
113
|
-
const iconSize = size * 0.55;
|
|
114
120
|
|
|
115
121
|
return (
|
|
116
|
-
<
|
|
122
|
+
<SvgIcon size={size}>
|
|
117
123
|
<Circle cx={12} cy={8} r={4} {...stroke} />
|
|
118
124
|
<Path d="M4 20c0-4 3.582-7 8-7s8 3 8 7" {...stroke} />
|
|
119
|
-
</
|
|
125
|
+
</SvgIcon>
|
|
120
126
|
);
|
|
121
127
|
};
|
|
122
128
|
|
|
123
129
|
export const defaultIcons: Record<TerraSemanticIconName, TerraIconComponent> = {
|
|
124
|
-
"
|
|
125
|
-
"
|
|
126
|
-
"
|
|
130
|
+
"terra.chevron-left": ChevronLeftIcon,
|
|
131
|
+
"terra.chevron-right": ChevronRightIcon,
|
|
132
|
+
"terra.x": XIcon,
|
|
133
|
+
"terra.check": CheckIcon,
|
|
134
|
+
"terra.person": PersonIcon,
|
|
127
135
|
"status.info": InfoIcon,
|
|
128
136
|
"status.success": SuccessIcon,
|
|
129
137
|
"status.warning": WarningIcon,
|
|
130
138
|
"status.danger": DangerIcon,
|
|
131
|
-
person: PersonIcon,
|
|
132
139
|
};
|
package/src/theme/types.ts
CHANGED
|
@@ -412,14 +412,15 @@ declare global {
|
|
|
412
412
|
export interface TerraIconRegistry extends TerraUI.IconRegistry {}
|
|
413
413
|
|
|
414
414
|
export type TerraSemanticIconName =
|
|
415
|
-
| "
|
|
416
|
-
| "
|
|
417
|
-
| "
|
|
415
|
+
| "terra.chevron-left"
|
|
416
|
+
| "terra.chevron-right"
|
|
417
|
+
| "terra.x"
|
|
418
|
+
| "terra.check"
|
|
419
|
+
| "terra.person"
|
|
418
420
|
| "status.info"
|
|
419
421
|
| "status.success"
|
|
420
422
|
| "status.warning"
|
|
421
|
-
| "status.danger"
|
|
422
|
-
| "person";
|
|
423
|
+
| "status.danger";
|
|
423
424
|
|
|
424
425
|
export type TerraConfiguredIconName = keyof TerraIconRegistry & string;
|
|
425
426
|
|