@proulr/sdui-components 0.1.73 → 0.1.76
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/package.json +1 -1
- package/src/renderer/blocks/SduiDiscoverBlocks.tsx +1 -1
- package/src/renderer/blocks/SduiPrimitiveBlocks.tsx +7 -1
- package/src/renderer/theme/proulr-theme.ts +5 -0
- package/src/renderer/ui/Skeleton.tsx +108 -0
- package/src/renderer/ui/TextField.tsx +11 -2
- package/src/renderer/ui/compose-pill-input.tsx +2 -0
- package/src/renderer/ui/compose-text-area.tsx +7 -1
- package/src/renderer/ui/index.ts +8 -0
package/package.json
CHANGED
|
@@ -71,7 +71,7 @@ function readProfile(data: Record<string, unknown>, ref?: string): DiscoverNearb
|
|
|
71
71
|
|
|
72
72
|
function PresenceDot({ status }: { status: ReturnType<typeof getPresenceStatus> }) {
|
|
73
73
|
const dotColor =
|
|
74
|
-
status === 'online' ? m3Colors.online : status === 'recent' ? m3Colors.
|
|
74
|
+
status === 'online' ? m3Colors.online : status === 'recent' ? m3Colors.presenceRecent : m3Colors.outline;
|
|
75
75
|
return <View style={[styles.presenceDot, { backgroundColor: dotColor }]} />;
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -89,9 +89,15 @@ export function SduiScrollBlock({ block, children }: SduiBlockRenderProps) {
|
|
|
89
89
|
? { paddingHorizontal: padding, gap }
|
|
90
90
|
: { padding, gap };
|
|
91
91
|
|
|
92
|
+
const scrollViewProps = chromeScroll?.scrollViewProps ?? {};
|
|
93
|
+
const { ref: scrollRef, ...restScrollViewProps } = scrollViewProps as {
|
|
94
|
+
ref?: React.Ref<ScrollView>;
|
|
95
|
+
} & Record<string, unknown>;
|
|
96
|
+
|
|
92
97
|
return (
|
|
93
98
|
<SduiLayoutDebugFrame layer="scroll" style={{ flex: 1, minHeight: 0, alignSelf: 'stretch' }}>
|
|
94
99
|
<ScrollView
|
|
100
|
+
ref={scrollRef}
|
|
95
101
|
style={[
|
|
96
102
|
styles.scroll,
|
|
97
103
|
resolveViewStyleProps(block.props.style as Record<string, unknown>),
|
|
@@ -105,7 +111,7 @@ export function SduiScrollBlock({ block, children }: SduiBlockRenderProps) {
|
|
|
105
111
|
: blockContentStyle
|
|
106
112
|
}
|
|
107
113
|
{...(chromeScroll?.scrollInsetProps ?? {})}
|
|
108
|
-
{...
|
|
114
|
+
{...restScrollViewProps}
|
|
109
115
|
onScroll={chromeScroll?.onScroll}
|
|
110
116
|
scrollEventThrottle={chromeScroll?.onScroll || chromeScroll?.scrollViewProps ? 16 : undefined}
|
|
111
117
|
refreshControl={chromeScroll?.refreshControl}
|
|
@@ -32,6 +32,8 @@ export const m3Colors = {
|
|
|
32
32
|
scrim: 'rgba(0,0,0,0.78)',
|
|
33
33
|
storyInteractionScrim: 'rgba(0,0,0,0.48)',
|
|
34
34
|
online: '#34FF88',
|
|
35
|
+
/** Saiu recentemente (≤24h) — laranja, distinto do online verde e do ciano de marca. */
|
|
36
|
+
presenceRecent: '#FF9F0A',
|
|
35
37
|
} as const;
|
|
36
38
|
|
|
37
39
|
/** Accent/soft por reação Flash (conteúdo) — fonte única, sem hex solto nos apps. */
|
|
@@ -96,6 +98,9 @@ export const colors = {
|
|
|
96
98
|
headerEnd: '#D946EF',
|
|
97
99
|
online: '#34FF88',
|
|
98
100
|
onlineSoft: 'rgba(52,255,136,0.18)',
|
|
101
|
+
/** Presence: recently active (not online). */
|
|
102
|
+
presenceRecent: '#FF9F0A',
|
|
103
|
+
presenceRecentSoft: 'rgba(255,159,10,0.22)',
|
|
99
104
|
danger: '#FF4444',
|
|
100
105
|
dangerSoft: 'rgba(255,68,68,0.22)',
|
|
101
106
|
dangerSoft18: 'rgba(239,68,68,0.18)',
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { View, type DimensionValue, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
import Animated, {
|
|
4
|
+
Easing,
|
|
5
|
+
ReduceMotion,
|
|
6
|
+
useAnimatedStyle,
|
|
7
|
+
useSharedValue,
|
|
8
|
+
withRepeat,
|
|
9
|
+
withTiming,
|
|
10
|
+
} from 'react-native-reanimated';
|
|
11
|
+
import { m3Colors, m3Shape, spacing } from '../theme/proulr-theme';
|
|
12
|
+
|
|
13
|
+
const PULSE_DURATION_MS = 900;
|
|
14
|
+
const PULSE_MIN_OPACITY = 0.45;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Pulso de opacidade compartilhado por cada bloco de skeleton. Respeita o
|
|
18
|
+
* "reduzir movimento" do sistema (fica estatico). Como `react-native-reanimated`
|
|
19
|
+
* ja e usado nos primitivos do pacote (ex.: RangeSlider), importamos direto.
|
|
20
|
+
*/
|
|
21
|
+
function usePulseStyle() {
|
|
22
|
+
const progress = useSharedValue(PULSE_MIN_OPACITY);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
progress.value = withRepeat(
|
|
26
|
+
withTiming(1, {
|
|
27
|
+
duration: PULSE_DURATION_MS,
|
|
28
|
+
easing: Easing.inOut(Easing.ease),
|
|
29
|
+
reduceMotion: ReduceMotion.System,
|
|
30
|
+
}),
|
|
31
|
+
-1,
|
|
32
|
+
true,
|
|
33
|
+
);
|
|
34
|
+
}, [progress]);
|
|
35
|
+
|
|
36
|
+
return useAnimatedStyle(() => ({ opacity: progress.value }));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type SkeletonProps = {
|
|
40
|
+
width?: DimensionValue;
|
|
41
|
+
height?: DimensionValue;
|
|
42
|
+
radius?: number;
|
|
43
|
+
style?: StyleProp<ViewStyle>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Bloco tonal com shimmer — base de qualquer skeleton. */
|
|
47
|
+
export function Skeleton({
|
|
48
|
+
width = '100%',
|
|
49
|
+
height = 16,
|
|
50
|
+
radius = m3Shape.cornerSmall,
|
|
51
|
+
style,
|
|
52
|
+
}: SkeletonProps) {
|
|
53
|
+
const pulse = usePulseStyle();
|
|
54
|
+
return (
|
|
55
|
+
<Animated.View
|
|
56
|
+
accessibilityRole="none"
|
|
57
|
+
style={[
|
|
58
|
+
{ width, height, borderRadius: radius, backgroundColor: m3Colors.surfaceContainerHigh },
|
|
59
|
+
pulse,
|
|
60
|
+
style,
|
|
61
|
+
]}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type SkeletonCircleProps = {
|
|
67
|
+
size?: number;
|
|
68
|
+
style?: StyleProp<ViewStyle>;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** Placeholder circular (avatar). */
|
|
72
|
+
export function SkeletonCircle({ size = 48, style }: SkeletonCircleProps) {
|
|
73
|
+
return <Skeleton width={size} height={size} radius={size / 2} style={style} />;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type SkeletonTextProps = {
|
|
77
|
+
lines?: number;
|
|
78
|
+
lineHeight?: number;
|
|
79
|
+
gap?: number;
|
|
80
|
+
/** Largura da ultima linha (efeito paragrafo). */
|
|
81
|
+
lastLineWidth?: DimensionValue;
|
|
82
|
+
style?: StyleProp<ViewStyle>;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** Bloco de linhas de texto (ultima linha mais curta). */
|
|
86
|
+
export function SkeletonText({
|
|
87
|
+
lines = 3,
|
|
88
|
+
lineHeight = 12,
|
|
89
|
+
gap = spacing.xs,
|
|
90
|
+
lastLineWidth = '60%',
|
|
91
|
+
style,
|
|
92
|
+
}: SkeletonTextProps) {
|
|
93
|
+
return (
|
|
94
|
+
<View style={[{ gap }, style]}>
|
|
95
|
+
{Array.from({ length: Math.max(1, lines) }).map((_, index) => {
|
|
96
|
+
const isLast = index === lines - 1;
|
|
97
|
+
return (
|
|
98
|
+
<Skeleton
|
|
99
|
+
key={index}
|
|
100
|
+
height={lineHeight}
|
|
101
|
+
width={isLast && lines > 1 ? lastLineWidth : '100%'}
|
|
102
|
+
radius={m3Shape.cornerFull}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
})}
|
|
106
|
+
</View>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import {
|
|
3
|
+
Platform,
|
|
3
4
|
Pressable,
|
|
4
5
|
StyleSheet,
|
|
5
6
|
StyleProp,
|
|
@@ -11,6 +12,14 @@ import {
|
|
|
11
12
|
import { AppIcon, type AppIconName } from './app-icon';
|
|
12
13
|
import { fonts, m3Colors, m3Shape, spacing } from '../theme/proulr-theme';
|
|
13
14
|
|
|
15
|
+
const webInputLock =
|
|
16
|
+
Platform.OS === 'web'
|
|
17
|
+
? ({
|
|
18
|
+
WebkitTextFillColor: m3Colors.onSurface,
|
|
19
|
+
caretColor: m3Colors.onSurface,
|
|
20
|
+
} as object)
|
|
21
|
+
: null;
|
|
22
|
+
|
|
14
23
|
export function TextField(
|
|
15
24
|
props: TextInputProps & {
|
|
16
25
|
containerStyle?: StyleProp<ViewStyle>;
|
|
@@ -33,7 +42,7 @@ export function TextField(
|
|
|
33
42
|
placeholderTextColor={m3Colors.onSurfaceVariant}
|
|
34
43
|
multiline={multiline}
|
|
35
44
|
textAlignVertical={multiline ? 'top' : 'center'}
|
|
36
|
-
style={[styles.fieldInput, multiline && styles.fieldInputMultiline, style]}
|
|
45
|
+
style={[styles.fieldInput, multiline && styles.fieldInputMultiline, webInputLock, style]}
|
|
37
46
|
{...rest}
|
|
38
47
|
/>
|
|
39
48
|
</View>
|
|
@@ -54,7 +63,7 @@ export function PasswordField(
|
|
|
54
63
|
<TextInput
|
|
55
64
|
ref={inputRef}
|
|
56
65
|
placeholderTextColor={m3Colors.onSurfaceVariant}
|
|
57
|
-
style={[styles.fieldInput, styles.passwordInput]}
|
|
66
|
+
style={[styles.fieldInput, styles.passwordInput, webInputLock]}
|
|
58
67
|
secureTextEntry={!visible}
|
|
59
68
|
{...rest}
|
|
60
69
|
/>
|
|
@@ -8,7 +8,7 @@ export function ComposeTextArea(props: TextInputProps) {
|
|
|
8
8
|
placeholder="O que está acontecendo?"
|
|
9
9
|
placeholderTextColor={m3Colors.onSurfaceVariant}
|
|
10
10
|
multiline
|
|
11
|
-
style={styles.input}
|
|
11
|
+
style={[styles.input, Platform.OS === 'web' ? styles.inputWeb : null]}
|
|
12
12
|
{...(Platform.OS === 'android' ? { includeFontPadding: false } : {})}
|
|
13
13
|
{...props}
|
|
14
14
|
/>
|
|
@@ -56,6 +56,12 @@ const styles = StyleSheet.create({
|
|
|
56
56
|
textAlignVertical: 'top',
|
|
57
57
|
paddingTop: spacing.xs,
|
|
58
58
|
},
|
|
59
|
+
// Samsung/Xiaomi force-dark can leave caret text black on OLED — lock fill color on web.
|
|
60
|
+
inputWeb: {
|
|
61
|
+
WebkitTextFillColor: m3Colors.onSurface,
|
|
62
|
+
caretColor: m3Colors.onSurface,
|
|
63
|
+
outlineStyle: 'none',
|
|
64
|
+
} as object,
|
|
59
65
|
previewWrap: {
|
|
60
66
|
position: 'relative',
|
|
61
67
|
marginTop: spacing.xs,
|
package/src/renderer/ui/index.ts
CHANGED
|
@@ -59,6 +59,14 @@ export { ProgressBar } from './ProgressBar';
|
|
|
59
59
|
export { RangeSlider, type RangeSliderProps } from './RangeSlider';
|
|
60
60
|
export { SectionHeader } from './SectionHeader';
|
|
61
61
|
export { SelectRow, MultiSelectRow } from './SelectRow';
|
|
62
|
+
export {
|
|
63
|
+
Skeleton,
|
|
64
|
+
SkeletonCircle,
|
|
65
|
+
SkeletonText,
|
|
66
|
+
type SkeletonProps,
|
|
67
|
+
type SkeletonCircleProps,
|
|
68
|
+
type SkeletonTextProps,
|
|
69
|
+
} from './Skeleton';
|
|
62
70
|
export { StatRow } from './StatRow';
|
|
63
71
|
export { ToggleRow } from './ToggleRow';
|
|
64
72
|
export { PasswordField, TextField } from './TextField';
|