@astratra/native-ui 1.1.0 → 1.2.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/README.md +2 -1
- package/package.json +6 -2
- package/src/components/FeedbackBar.js +32 -5
- package/src/components/runtime.js +14 -0
package/README.md
CHANGED
|
@@ -490,7 +490,7 @@ Aucun texte, aucune icône, aucun chargement en dur : tout vient des props.
|
|
|
490
490
|
|
|
491
491
|
# Les retours
|
|
492
492
|
|
|
493
|
-
## FeedbackBar :
|
|
493
|
+
## FeedbackBar : pouces sous une réponse
|
|
494
494
|
|
|
495
495
|
Deux boutons en verre discrets sous la réponse d'une IA. Le pouce haut part
|
|
496
496
|
tout de suite ; le pouce bas ouvre une feuille (raison, commentaire facultatif)
|
|
@@ -521,6 +521,7 @@ import { FeedbackBar } from '@astratra/native-ui';
|
|
|
521
521
|
- **Haptique** : `expo-haptics` (pair facultatif) donne un léger tic ; `haptics={false}`
|
|
522
522
|
le coupe, `haptics={() => …}` le remplace. Sans le module, la barre est muette.
|
|
523
523
|
- **Style** : `accent`, `size`, `scheme`, `pageBackground`, `ink`, `icons`
|
|
524
|
+
- **Pouces dessinés, jamais d'émojis** : contour vectoriel (`react-native-svg`, dépendance facultative), plein une fois choisi ; sans `react-native-svg`, un simple triangle. `icons` (`{ up, down }`) remplace ce dessin par ceux de l'app.
|
|
524
525
|
(nœuds à la place des émojis).
|
|
525
526
|
- **Règles pures** (entrée `@astratra/native-ui/logic`, testées en Node) :
|
|
526
527
|
`FEEDBACK_REASONS`, `FEEDBACK_LABELS`, `resolveFeedbackLabels`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astratra/native-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Mobile UI kit for React Native / Expo: Apple's Liquid Glass on iOS and a calibrated visible surface on Android, glass buttons, pale cards, bars that fold on scroll, an Instagram-style tab bar, a collapsible header, a Markdown renderer for AI answers, pictures (natural-ratio images revealed gently, a shimmer placeholder, a full-screen viewer with pinch, double-tap and pull-to-close), and a thumbs up/down feedback bar for AI answers. Pure rules exported separately and tested in plain Node.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20"
|
|
@@ -56,11 +56,15 @@
|
|
|
56
56
|
"expo-linear-gradient": ">=14",
|
|
57
57
|
"react": ">=18",
|
|
58
58
|
"react-native": ">=0.76",
|
|
59
|
-
"react-native-reanimated": ">=3.16"
|
|
59
|
+
"react-native-reanimated": ">=3.16",
|
|
60
|
+
"react-native-svg": ">=15"
|
|
60
61
|
},
|
|
61
62
|
"peerDependenciesMeta": {
|
|
62
63
|
"expo-haptics": {
|
|
63
64
|
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"react-native-svg": {
|
|
67
|
+
"optional": true
|
|
64
68
|
}
|
|
65
69
|
},
|
|
66
70
|
"devDependencies": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Words come from the app (`labels`, `locale`); the haptic tick is optional
|
|
10
10
|
* (expo-haptics when installed, or the function the app passes).
|
|
11
11
|
*/
|
|
12
|
-
const { React, h, RN, useScheme } = require('./runtime');
|
|
12
|
+
const { React, h, RN, Svg, useScheme } = require('./runtime');
|
|
13
13
|
const { GlassButton } = require('./GlassButton');
|
|
14
14
|
const { usePaleCardColors } = require('./PaleCard');
|
|
15
15
|
const {
|
|
@@ -24,6 +24,33 @@ const {
|
|
|
24
24
|
|
|
25
25
|
const DEFAULT_ACCENT = '#3b6cf0';
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* The thumbs are drawn, never emoji (an emoji changes with the system and looks out of place in a
|
|
29
|
+
* product). One outline on a 24 grid; down is the same hand turned upside down; filled once chosen.
|
|
30
|
+
* Without react-native-svg: a plain triangle, still not an emoji.
|
|
31
|
+
*/
|
|
32
|
+
const THUMB_PATH = 'M7 21H4.5A1.5 1.5 0 0 1 3 19.5v-8A1.5 1.5 0 0 1 4.5 10H7M7 21V10l4.2-6.3A1.6 1.6 0 0 1 14 4.6V9h5a2 2 0 0 1 2 2.4l-1.5 7.9A2 2 0 0 1 17.5 21H7';
|
|
33
|
+
|
|
34
|
+
function thumbIcon(rating, { color, filled, size }) {
|
|
35
|
+
const side = Math.round(size * 0.5);
|
|
36
|
+
if (!Svg) {
|
|
37
|
+
return h(RN.Text, { style: { color, fontSize: Math.round(size * 0.36) } }, rating === 'down' ? '\u25BC' : '\u25B2');
|
|
38
|
+
}
|
|
39
|
+
return h(
|
|
40
|
+
Svg.Svg,
|
|
41
|
+
{ width: side, height: side, viewBox: '0 0 24 24' },
|
|
42
|
+
h(Svg.Path, {
|
|
43
|
+
d: THUMB_PATH,
|
|
44
|
+
stroke: color,
|
|
45
|
+
strokeWidth: 1.8,
|
|
46
|
+
strokeLinecap: 'round',
|
|
47
|
+
strokeLinejoin: 'round',
|
|
48
|
+
fill: filled ? color : 'none',
|
|
49
|
+
transform: rating === 'down' ? 'matrix(1 0 0 -1 0 24)' : undefined
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
27
54
|
/* expo-haptics is an optional peer: absent, the bar is silent. */
|
|
28
55
|
let cachedHaptics;
|
|
29
56
|
function loadHaptics() {
|
|
@@ -99,7 +126,7 @@ function FeedbackBar({
|
|
|
99
126
|
if (onSubmit) onSubmit(payload);
|
|
100
127
|
};
|
|
101
128
|
|
|
102
|
-
const thumb = (rating
|
|
129
|
+
const thumb = (rating) => {
|
|
103
130
|
const state = feedbackButtonState({ value, rating, labels: words, disabled });
|
|
104
131
|
return h(
|
|
105
132
|
GlassButton,
|
|
@@ -114,7 +141,7 @@ function FeedbackBar({
|
|
|
114
141
|
testID: testID ? `${testID}-${rating}` : undefined,
|
|
115
142
|
onPress: () => press(rating)
|
|
116
143
|
},
|
|
117
|
-
icons && icons[rating] ? icons[rating] :
|
|
144
|
+
icons && icons[rating] ? icons[rating] : thumbIcon(rating, { color: state.selected ? '#ffffff' : textColor, filled: state.selected, size })
|
|
118
145
|
);
|
|
119
146
|
};
|
|
120
147
|
|
|
@@ -141,8 +168,8 @@ function FeedbackBar({
|
|
|
141
168
|
return h(
|
|
142
169
|
RN.View,
|
|
143
170
|
{ testID, accessibilityRole: 'toolbar', accessibilityLabel: words.group, style: [{ flexDirection: 'row', alignItems: 'center', gap: 8 }, style] },
|
|
144
|
-
thumb('up'
|
|
145
|
-
thumb('down'
|
|
171
|
+
thumb('up'),
|
|
172
|
+
thumb('down'),
|
|
146
173
|
h(
|
|
147
174
|
RN.Modal,
|
|
148
175
|
{ visible: asking, transparent: true, animationType: 'slide', onRequestClose: close },
|
|
@@ -72,7 +72,21 @@ function useReducedMotion() {
|
|
|
72
72
|
return typeof hook === 'function' ? Boolean(hook()) : false;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* react-native-svg is an optional peer: it draws the icons (the feedback thumbs). Without it, those
|
|
77
|
+
* components fall back to a plain shape, never to an emoji.
|
|
78
|
+
*/
|
|
79
|
+
let Svg = null;
|
|
80
|
+
try {
|
|
81
|
+
const svg = require('react-native-svg');
|
|
82
|
+
const Racine = svg.Svg || interop(svg);
|
|
83
|
+
if (Racine && svg.Path) Svg = { Svg: Racine, Path: svg.Path };
|
|
84
|
+
} catch {
|
|
85
|
+
Svg = null;
|
|
86
|
+
}
|
|
87
|
+
|
|
75
88
|
module.exports = {
|
|
89
|
+
Svg,
|
|
76
90
|
React,
|
|
77
91
|
RN,
|
|
78
92
|
h,
|