@astratra/native-ui 0.1.0 → 0.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astratra/native-ui",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
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, and a Markdown renderer for AI answers. Pure rules exported separately and tested in plain Node.",
5
5
  "engines": {
6
6
  "node": ">=20"
@@ -8,7 +8,7 @@
8
8
  * order: the tint, the highlight, then the content. The content goes ABOVE
9
9
  * the highlight — a white veil over the icon would dim it.
10
10
  */
11
- const { h, RN, LinearGradient, getGlassMode, useScheme } = require('./runtime');
11
+ const { h, RN, LinearGradient, getGlassMode, useScheme, Reanimated, AnimatedView, useReducedMotion } = require('./runtime');
12
12
  const { GlassSurface } = require('./GlassSurface');
13
13
  const { glassButtonMaterial, glassButtonTint } = require('../logic/glass');
14
14
 
@@ -31,9 +31,15 @@ const control = {
31
31
  paddingHorizontal: 12
32
32
  };
33
33
 
34
+ const { useAnimatedStyle, useSharedValue, withSpring } = Reanimated;
35
+
34
36
  /* The press is iOS's gesture; on Android the ripple is enough, and both
35
- together make a button that jerks. */
36
- const PRESSED = { opacity: 0.75, transform: [{ scale: 0.96 }] };
37
+ together make a button that jerks. It moves, never jumps: the finger sinks
38
+ the content on a spring with no bounce, the release brings it back on a
39
+ short one that overshoots by a hair. With "Reduce Motion" it only dims. */
40
+ const PRESSED = { opacity: 0.75, scale: 0.96 };
41
+ const PRESS_IN = { stiffness: 600, damping: 2 * Math.sqrt(600), mass: 1 };
42
+ const RELEASE = { stiffness: 380, damping: 20, mass: 1 };
37
43
 
38
44
  function GlassButton({
39
45
  children,
@@ -53,6 +59,20 @@ function GlassButton({
53
59
  const mode = getGlassMode();
54
60
  const material = glassButtonMaterial({ mode, scheme, tinted: Boolean(tint) });
55
61
  const android = RN.Platform.OS === 'android';
62
+ const reduceMotion = useReducedMotion();
63
+ /* 0 at rest, 1 under the finger. */
64
+ const press = useSharedValue(0);
65
+ const pressed = useAnimatedStyle(() => {
66
+ const depth = Math.min(Math.max(press.value, 0), 1);
67
+ return {
68
+ opacity: 1 - (1 - PRESSED.opacity) * depth,
69
+ transform: [{ scale: reduceMotion ? 1 : 1 - (1 - PRESSED.scale) * press.value }]
70
+ };
71
+ });
72
+ const sink = (to, spring) => {
73
+ if (android) return;
74
+ press.value = withSpring(to, spring);
75
+ };
56
76
  return h(
57
77
  GlassSurface,
58
78
  {
@@ -94,6 +114,8 @@ function GlassButton({
94
114
  hitSlop: 6,
95
115
  onPress,
96
116
  onLongPress,
117
+ onPressIn: () => sink(1, PRESS_IN),
118
+ onPressOut: () => sink(0, RELEASE),
97
119
  /* Without it the button had no touch feedback at all on Android. The
98
120
  ripple is light on a tinted button, dark on a light one. */
99
121
  android_ripple: {
@@ -101,15 +123,14 @@ function GlassButton({
101
123
  borderless: true,
102
124
  foreground: true
103
125
  },
104
- style: ({ pressed }) => [
126
+ style: [
105
127
  control,
106
128
  /* With no fill of its own, iOS casts this shadow onto the silhouette
107
129
  of the icon and text only: the glow that lifts them out. */
108
- material.glow,
109
- pressed && !android ? PRESSED : null
130
+ material.glow
110
131
  ]
111
132
  },
112
- children
133
+ h(AnimatedView, { style: [control, pressed] }, children)
113
134
  )
114
135
  );
115
136
  }