@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 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 : 👍 / 👎 sous une réponse
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.1.0",
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, glyph) => {
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] : h(RN.Text, { style: { fontSize: Math.round(size * 0.46) } }, glyph)
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', '\u{1F44D}'),
145
- thumb('down', '\u{1F44E}'),
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,