@gryt/ui-native 0.3.1 → 0.5.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.
Files changed (46) hide show
  1. package/README.md +9 -1
  2. package/dist/components/AlertDialog/AlertDialog.d.ts.map +1 -1
  3. package/dist/components/Checkbox/Checkbox.d.ts +7 -1
  4. package/dist/components/Checkbox/Checkbox.d.ts.map +1 -1
  5. package/dist/components/Checkbox/Checkbox.js +47 -15
  6. package/dist/components/Dialog/Dialog.d.ts.map +1 -1
  7. package/dist/components/Dialog/Dialog.js +10 -1
  8. package/dist/components/Drawer/Drawer.d.ts +0 -7
  9. package/dist/components/Drawer/Drawer.d.ts.map +1 -1
  10. package/dist/components/Drawer/Drawer.js +229 -30
  11. package/dist/components/Progress/Progress.d.ts +11 -17
  12. package/dist/components/Progress/Progress.d.ts.map +1 -1
  13. package/dist/components/Progress/Progress.js +66 -22
  14. package/dist/components/Radio/Radio.d.ts +9 -1
  15. package/dist/components/Radio/Radio.d.ts.map +1 -1
  16. package/dist/components/Radio/Radio.js +38 -14
  17. package/dist/components/ScrollArea/ScrollArea.d.ts +6 -4
  18. package/dist/components/ScrollArea/ScrollArea.d.ts.map +1 -1
  19. package/dist/components/ScrollArea/ScrollArea.js +19 -4
  20. package/dist/components/Sheet/Sheet.d.ts +99 -0
  21. package/dist/components/Sheet/Sheet.d.ts.map +1 -0
  22. package/dist/components/Sheet/Sheet.js +201 -0
  23. package/dist/components/Slider/Slider.d.ts.map +1 -1
  24. package/dist/components/Slider/Slider.js +33 -0
  25. package/dist/components/Switch/Switch.d.ts +4 -1
  26. package/dist/components/Switch/Switch.d.ts.map +1 -1
  27. package/dist/components/Switch/Switch.js +21 -17
  28. package/dist/components/Tabs/Tabs.d.ts +38 -13
  29. package/dist/components/Tabs/Tabs.d.ts.map +1 -1
  30. package/dist/components/Tabs/Tabs.js +157 -42
  31. package/dist/components/Toggle/Toggle.d.ts +0 -1
  32. package/dist/components/Toggle/Toggle.d.ts.map +1 -1
  33. package/dist/components/Toggle/Toggle.js +15 -1
  34. package/dist/components/internal/ControlRow.d.ts +54 -0
  35. package/dist/components/internal/ControlRow.d.ts.map +1 -0
  36. package/dist/components/internal/ControlRow.js +59 -0
  37. package/dist/components/internal/dragLock.d.ts +43 -0
  38. package/dist/components/internal/dragLock.d.ts.map +1 -0
  39. package/dist/components/internal/dragLock.js +18 -0
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +2 -0
  43. package/dist/motion/motion.d.ts +13 -3
  44. package/dist/motion/motion.d.ts.map +1 -1
  45. package/dist/motion/motion.js +6 -6
  46. package/package.json +11 -5
package/README.md CHANGED
@@ -69,7 +69,6 @@ goal, so the honest list is worth more than a claim of parity that isn't one.
69
69
  | `Button` | Press scale is kept, hover scale is dropped | The web scales to `0.96` on press and `1.03` on hover. Press maps directly onto `onPressIn`/`onPressOut`, so it is here, spring-animated and skipped when reduce-motion is on. |
70
70
  | `Button` | `hasPopup` is a prop, not inferred | The web reads `aria-haspopup`, which Base UI sets on a trigger, and skips the scale so the trigger does not drag its own popup sideways while it is being measured. React Native has no such attribute, so a menu trigger has to say so. |
71
71
  | `TextField` | Focus changes the border colour instead of drawing an outline | The web's `outline` sits outside the box and shifts nothing. React Native has no outline, and a second ring would move the layout on focus. |
72
- | `Progress` | Indeterminate renders as an empty track | The web sweeps a partial bar with a CSS keyframe. Doing it here needs an `Animated` loop plus a reduce-motion check, which deserves its own pass rather than a footnote. |
73
72
  | `Tooltip` | Opens on long press, closes on release, instead of appearing on hover after a delay | A phone has no hover. This is the same name on a different interaction, and it is the component where 1:1 is least achievable. An interface that needs tooltips to be usable will not survive the port. |
74
73
  | *(positioned overlays)* | The popup does not follow a trigger that moves | Floating UI keeps watching the reference element. React Native measures on demand and reports nothing afterwards, so the position is taken once, when the popup opens. If a list scrolls underneath an open menu, the menu stays put. |
75
74
  | `Popover` | `Arrow` renders nothing | Base UI positions an arrow against the popup's edge once Floating UI has settled. Here it means a rotated square, a border on two sides only, and knowing which side the popup landed on. Doable, not free, and not done. |
@@ -89,6 +88,15 @@ goal, so the honest list is worth more than a claim of parity that isn't one.
89
88
  | `Accordion` | Two glyphs instead of a rotating chevron | Same reasoning as the checkbox tick: rotating one needs a transform and a measurement, and there is no icon set here yet. |
90
89
  | `Skeleton` | Does not pulse | A loop running for the length of a request costs battery, and it is the sort of motion reduce-motion users switch off first. A flat block still reads as loading. |
91
90
 
91
+ ## Additions
92
+
93
+ The other direction, and a shorter list. Something here that the web does not
94
+ have needs the same justification as something missing.
95
+
96
+ | Component | Why there is no web counterpart |
97
+ |---|---|
98
+ | `Sheet` | A sheet is what a phone does where the web opens a dialog or slides a drawer, and the two are not the same interaction: it is dragged, it settles at heights the user chooses, and dismissing it is a flick rather than a click on an X. Shipping the web's Dialog on a phone would be 1:1 and wrong; inventing a sheet on the web would be worse. |
99
+
92
100
  ## Not Node-importable
93
101
 
94
102
  The build is unbundled `tsc` output, which is what Metro wants: it resolves
@@ -1 +1 @@
1
- {"version":3,"file":"AlertDialog.d.ts","sourceRoot":"","sources":["../../../src/components/AlertDialog/AlertDialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEvF;;;;;;;;;;GAUG;AAIH,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;AAE1E,iBAAS,KAAK,CAAC,KAAK,EAAE,qBAAqB,+BAE1C;AAED,MAAM,MAAM,oBAAoB,GAAG,eAAe,CAAC;AAEnD,eAAO,MAAM,WAAW;;;;gBAUw0B,CAAC;;;;;gBAAk6H,CAAC;aAAkB,CAAC;;;gBAAiS,CAAC;aAAkB,CAAC;;;gBAAkiB,CAAC;aAAkB,CAAC;;CADjoL,CAAC"}
1
+ {"version":3,"file":"AlertDialog.d.ts","sourceRoot":"","sources":["../../../src/components/AlertDialog/AlertDialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEvF;;;;;;;;;;GAUG;AAIH,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;AAE1E,iBAAS,KAAK,CAAC,KAAK,EAAE,qBAAqB,+BAE1C;AAED,MAAM,MAAM,oBAAoB,GAAG,eAAe,CAAC;AAEnD,eAAO,MAAM,WAAW;;;;gBAUw0B,CAAC;;;;;gBAAovK,CAAC;aAAkB,CAAC;;;gBAAiS,CAAC;aAAkB,CAAC;;;gBAAkiB,CAAC;aAAkB,CAAC;;CADn9N,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { type ReactNode } from "react";
1
2
  import { type StyleProp, type ViewStyle } from "react-native";
2
3
  export type CheckboxTone = "primary" | "secondary" | "neutral" | "danger";
3
4
  export interface CheckboxProps {
@@ -8,6 +9,11 @@ export interface CheckboxProps {
8
9
  onCheckedChange?: (checked: boolean) => void;
9
10
  disabled?: boolean;
10
11
  tone?: CheckboxTone;
12
+ /**
13
+ * Tapping this toggles the box, which is what a `<label>` does on the web.
14
+ * See ControlRow for why it is a prop here rather than a wrapper element.
15
+ */
16
+ label?: ReactNode;
11
17
  style?: StyleProp<ViewStyle>;
12
18
  accessibilityLabel?: string;
13
19
  }
@@ -20,5 +26,5 @@ export interface CheckboxProps {
20
26
  * react-native-svg on top. Both are decisions for whoever needs a full icon set,
21
27
  * not for a checkbox.
22
28
  */
23
- export declare function Checkbox({ checked: controlled, defaultChecked, indeterminate, onCheckedChange, disabled, tone, style, accessibilityLabel, }: CheckboxProps): import("react").JSX.Element;
29
+ export declare function Checkbox({ checked: controlled, defaultChecked, indeterminate, onCheckedChange, disabled, tone, label, style, accessibilityLabel, }: CheckboxProps): import("react").JSX.Element;
24
30
  //# sourceMappingURL=Checkbox.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Checkbox.d.ts","sourceRoot":"","sources":["../../../src/components/Checkbox/Checkbox.tsx"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAI/E,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE1E,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,sEAAsE;IACtE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAID;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EAAE,UAAU,EACnB,cAAsB,EACtB,aAAqB,EACrB,eAAe,EACf,QAAQ,EACR,IAAgB,EAChB,KAAK,EACL,kBAAkB,GACnB,EAAE,aAAa,+BA2Cf"}
1
+ {"version":3,"file":"Checkbox.d.ts","sourceRoot":"","sources":["../../../src/components/Checkbox/Checkbox.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC5D,OAAO,EAAQ,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AASpE,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE1E,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,sEAAsE;IACtE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAID;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EAAE,UAAU,EACnB,cAAsB,EACtB,aAAqB,EACrB,eAAe,EACf,QAAQ,EACR,IAAgB,EAChB,KAAK,EACL,KAAK,EACL,kBAAkB,GACnB,EAAE,aAAa,+BAiFf"}
@@ -1,7 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useState } from "react";
3
- import { Pressable, Text } from "react-native";
2
+ import { useEffect, useState } from "react";
3
+ import { Text } from "react-native";
4
+ import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
5
+ import { grytScaleSteps } from "@gryt/theme";
6
+ import { springy } from "../../motion/index.js";
4
7
  import { toneRamp, useTheme } from "../../theme/index.js";
8
+ import { useReducedMotion } from "../../hooks/useReducedMotion.js";
9
+ import { ControlRow } from "../internal/ControlRow.js";
5
10
  const SIZE = 20;
6
11
  /**
7
12
  * The tick is a text glyph rather than an icon.
@@ -12,32 +17,59 @@ const SIZE = 20;
12
17
  * react-native-svg on top. Both are decisions for whoever needs a full icon set,
13
18
  * not for a checkbox.
14
19
  */
15
- export function Checkbox({ checked: controlled, defaultChecked = false, indeterminate = false, onCheckedChange, disabled, tone = "primary", style, accessibilityLabel, }) {
20
+ export function Checkbox({ checked: controlled, defaultChecked = false, indeterminate = false, onCheckedChange, disabled, tone = "primary", label, style, accessibilityLabel, }) {
16
21
  const theme = useTheme();
22
+ const reducedMotion = useReducedMotion();
17
23
  const [uncontrolled, setUncontrolled] = useState(defaultChecked);
18
24
  const checked = controlled ?? uncontrolled;
19
25
  const ramp = toneRamp(theme, tone);
20
26
  const filled = checked || indeterminate;
21
- return (_jsx(Pressable, { accessibilityRole: "checkbox", accessibilityState: {
22
- checked: indeterminate ? "mixed" : checked,
23
- disabled: !!disabled,
24
- }, accessibilityLabel: accessibilityLabel, disabled: disabled, onPress: () => {
27
+ /**
28
+ * The tick scales from 0, not from something near 1.
29
+ *
30
+ * Straight from the web's comment, and it is not a detail: the spring's
31
+ * overshoot is a percentage of the travel, so 0 → 1 overshoots to 1.12 and
32
+ * visibly springs, while 0.95 → 1 overshoots by 0.006 and does nothing at
33
+ * all. It used to appear instantly here, which is the same bug with the
34
+ * travel set to zero.
35
+ */
36
+ const tick = useSharedValue(filled ? 1 : 0);
37
+ useEffect(() => {
38
+ const to = filled ? 1 : 0;
39
+ // eslint-disable-next-line react-hooks/immutability
40
+ tick.value = reducedMotion ? to : springy(to);
41
+ }, [filled, tick, reducedMotion]);
42
+ const tickStyle = useAnimatedStyle(() => ({
43
+ transform: [{ scale: tick.value }],
44
+ opacity: tick.value,
45
+ }));
46
+ return (_jsx(ControlRow, { label: label, onPress: () => {
25
47
  const next = !checked;
26
48
  if (controlled === undefined)
27
49
  setUncontrolled(next);
28
50
  onCheckedChange?.(next);
29
- }, style: [
30
- {
51
+ }, disabled: disabled, pressScale: grytScaleSteps.checkbox.press, accessibilityRole: "checkbox", accessibilityState: {
52
+ checked: indeterminate ? "mixed" : checked,
53
+ disabled: !!disabled,
54
+ }, accessibilityLabel: accessibilityLabel, style: style, children: _jsx(Animated.View, { style: {
31
55
  width: SIZE,
32
56
  height: SIZE,
33
57
  borderRadius: theme.radius.sm,
34
58
  alignItems: "center",
35
59
  justifyContent: "center",
36
- borderWidth: filled ? 0 : 1.5,
37
- borderColor: theme.color.border,
38
- backgroundColor: filled ? ramp[8] : "transparent",
60
+ // The web keeps the border at 1 always and makes it transparent when
61
+ // checked, rather than dropping to 0 — a border that disappears
62
+ // changes the box's size mid-transition.
63
+ borderWidth: 1,
64
+ borderColor: filled ? "transparent" : theme.color.border,
65
+ // Was `transparent`, which is not what the web does: an unchecked box
66
+ // is a filled surface with an outline, not a hole.
67
+ backgroundColor: filled ? ramp[8] : theme.color.surfaceRaised,
39
68
  opacity: disabled ? 0.5 : 1,
40
- },
41
- style,
42
- ], children: filled ? (_jsx(Text, { style: { color: theme.color.bg, fontSize: 13, fontWeight: "900", lineHeight: 15 }, children: indeterminate ? "–" : "✓" })) : null }));
69
+ }, children: _jsx(Animated.View, { style: tickStyle, children: _jsx(Text, { style: {
70
+ color: theme.color.onAccent,
71
+ fontSize: 13,
72
+ fontWeight: "900",
73
+ lineHeight: 15,
74
+ }, children: indeterminate ? "–" : "✓" }) }) }) }));
43
75
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../../../src/components/Dialog/Dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,EAML,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AA2B/E,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,iBAAS,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,EAAE,EAAE,eAAe,+BAKxD;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,iBAAS,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,kBAAkB,+BAOvD;AAED;;;;;;GAMG;AACH,iBAAS,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,+BAErD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gEAAgE;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,iBAAS,KAAK,CAAC,EACb,QAAQ,EACR,KAAK,EACL,WAAkB,EAClB,UAAiB,GAClB,EAAE,gBAAgB,+BAwFlB;AAED;;;;;;GAMG;AACH,iBAAS,QAAQ,IAAI,IAAI,CAExB;AAED,iBAAS,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CAAE,+BAUzF;AAED,iBAAS,WAAW,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CAAE,+BAO/F;AAED,iBAAS,MAAM,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CAAE,+BAY1F;AAED,iBAAS,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CAAE,+BAOzF;AAED,eAAO,MAAM,MAAM;;;;;;;;;;CAUlB,CAAC"}
1
+ {"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../../../src/components/Dialog/Dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,EAML,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AA2B/E,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,iBAAS,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,EAAE,EAAE,eAAe,+BAKxD;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,iBAAS,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,kBAAkB,+BAOvD;AAED;;;;;;GAMG;AACH,iBAAS,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,+BAErD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gEAAgE;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,iBAAS,KAAK,CAAC,EACb,QAAQ,EACR,KAAK,EACL,WAAkB,EAClB,UAAiB,GAClB,EAAE,gBAAgB,+BA4GlB;AAYD;;;;;;GAMG;AACH,iBAAS,QAAQ,IAAI,IAAI,CAExB;AAED,iBAAS,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CAAE,+BAUzF;AAED,iBAAS,WAAW,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CAAE,+BAO/F;AAED,iBAAS,MAAM,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CAAE,+BAY1F;AAED,iBAAS,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CAAE,+BAOzF;AAED,eAAO,MAAM,MAAM;;;;;;;;;;CAUlB,CAAC"}
@@ -81,7 +81,16 @@ function Popup({ children, style, dismissible = true, scrollable = true, }) {
81
81
  justifyContent: "center",
82
82
  padding: theme.space(5),
83
83
  backgroundColor: "rgba(0,0,0,0.6)",
84
- }, children: _jsx(Pressable, { onPress: () => { }, style: { width: "100%", alignItems: "center", maxHeight: "80%" }, children: body }) }) }));
84
+ }, children: _jsx(View, { onStartShouldSetResponder: claimTouch, style: { width: "100%", alignItems: "center", maxHeight: "80%" }, children: body }) }) }));
85
+ }
86
+ /**
87
+ * Claim a touch that no child wanted.
88
+ *
89
+ * Declared once rather than as an inline arrow, so every Dialog is not handing
90
+ * the responder system a new function identity on every render.
91
+ */
92
+ function claimTouch() {
93
+ return true;
85
94
  }
86
95
  /**
87
96
  * Not rendered.
@@ -21,13 +21,6 @@ export interface DrawerPopupProps {
21
21
  dismissible?: boolean;
22
22
  style?: StyleProp<ViewStyle>;
23
23
  }
24
- /**
25
- * Slides in from an edge, which is the one place an animation is not decoration.
26
- *
27
- * A drawer that appears without moving reads as a screen change rather than a
28
- * panel, so this one animates even though Collapsible and Skeleton do not.
29
- * Reduce-motion still turns it off: the drawer arrives in place instead.
30
- */
31
24
  declare function Popup({ children, side, size, dismissible, style }: DrawerPopupProps): import("react").JSX.Element;
32
25
  declare function Close({ children, style }: {
33
26
  children?: ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"Drawer.d.ts","sourceRoot":"","sources":["../../../src/components/Drawer/Drawer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAQtB,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG/E,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAgBrD,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,iBAAS,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,EAAE,EAAE,eAAe,+BAKxD;AAED,iBAAS,OAAO,CAAC,EACf,QAAQ,EACR,KAAK,EACN,EAAE;IACD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,+BAWA;AAED,iBAAS,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,+BAErD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,iBAAS,KAAK,CAAC,EACb,QAAQ,EACR,IAAa,EACb,IAAU,EACV,WAAkB,EAClB,KAAK,EACN,EAAE,gBAAgB,+BA0ElB;AAED,iBAAS,KAAK,CAAC,EACb,QAAQ,EACR,KAAK,EACN,EAAE;IACD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,+BAWA;AAED,eAAO,MAAM,MAAM;;;;;;CAA0C,CAAC"}
1
+ {"version":3,"file":"Drawer.d.ts","sourceRoot":"","sources":["../../../src/components/Drawer/Drawer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAA2C,MAAM,OAAO,CAAC;AAC/F,OAAO,EAKL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAWtB,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG/E,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAgBrD,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,iBAAS,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,EAAE,EAAE,eAAe,+BAKxD;AAED,iBAAS,OAAO,CAAC,EACf,QAAQ,EACR,KAAK,EACN,EAAE;IACD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,+BAWA;AAED,iBAAS,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,+BAErD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAgBD,iBAAS,KAAK,CAAC,EACb,QAAQ,EACR,IAAa,EACb,IAAU,EACV,WAAkB,EAClB,KAAK,EACN,EAAE,gBAAgB,+BA2RlB;AAED,iBAAS,KAAK,CAAC,EACb,QAAQ,EACR,KAAK,EACN,EAAE;IACD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,+BAWA;AAED,eAAO,MAAM,MAAM;;;;;;CAA0C,CAAC"}
@@ -1,9 +1,11 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { createContext, useContext, useEffect } from "react";
3
- import { Dimensions, Modal, Pressable } from "react-native";
4
- import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
2
+ import { createContext, useContext, useEffect, useRef, useState } from "react";
3
+ import { Dimensions, PanResponder, Modal, Pressable } from "react-native";
4
+ import Animated, { runOnJS, useAnimatedStyle, useSharedValue } from "react-native-reanimated";
5
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
5
6
  import { useReducedMotion } from "../../hooks/useReducedMotion.js";
6
- import { durations, springy } from "../../motion/index.js";
7
+ import { grytDrawerBleed } from "@gryt/theme";
8
+ import { durations, travel as travelTo } from "../../motion/index.js";
7
9
  import { useOpenState } from "../../overlay/useOpenState.js";
8
10
  import { useTheme } from "../../theme/index.js";
9
11
  const DrawerContext = createContext(null);
@@ -31,45 +33,233 @@ function Portal({ children }) {
31
33
  * panel, so this one animates even though Collapsible and Skeleton do not.
32
34
  * Reduce-motion still turns it off: the drawer arrives in place instead.
33
35
  */
36
+ /**
37
+ * The scrim takes an animated opacity, so it has to be an animated component.
38
+ * Declared once at module scope — `createAnimatedComponent` inside a render
39
+ * makes a new component type every time, which remounts the subtree.
40
+ */
41
+ const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
34
42
  function Popup({ children, side = "left", size = 0.8, dismissible = true, style }) {
35
43
  const { open, setOpen } = useDrawer("Popup");
36
44
  const theme = useTheme();
45
+ /**
46
+ * The phone's own furniture, which a drawer has to stay clear of.
47
+ *
48
+ * A panel from the side is full height by definition, so its first row sits
49
+ * under the Dynamic Island and its last under the home indicator unless it
50
+ * says otherwise. A panel from the bottom only has the second problem.
51
+ *
52
+ * This is the same call GRYT-402 made for `Sheet`, and it belongs here for
53
+ * the same reason: every caller would otherwise write the same two lines, and
54
+ * the one that forgets ships a drawer with its heading under the clock. That
55
+ * is how the mobile shell's server switcher first looked.
56
+ */
57
+ const insets = useSafeAreaInsets();
37
58
  const reducedMotion = useReducedMotion();
38
59
  const screen = Dimensions.get("window");
39
60
  const vertical = side === "bottom";
40
61
  const extent = vertical ? screen.height * size : screen.width * size;
41
62
  const progress = useSharedValue(0);
42
- const hidden = side === "right" ? extent : side === "left" ? -extent : extent;
43
- // 700ms, the web's `--gryt-dur-spring-soft`, on the overshooting curve
44
- // which is what the web uses here despite the panel travelling its own
45
- // width. Matching it rather than substituting the tight curve, because the
46
- // brief is 1:1 with the web and not a second opinion about it.
63
+ /**
64
+ * The panel is built `grytDrawerBleed` larger than it needs to be and hangs
65
+ * that much off-screen, with matching padding so the content sits where it
66
+ * would have.
67
+ *
68
+ * The spring overshoots — that is what makes it a spring — and it settles
69
+ * onto its target from both directions. A panel sized exactly to its resting
70
+ * place therefore shows a seam of backdrop down its edge on the undershoot,
71
+ * for a frame or two, every time it opens. The web has had this since the
72
+ * Drawer was written, as `--gryt-drawer-bleed`; this side did not.
73
+ *
74
+ * `grytDrawerBleed` rather than a local 64, and `bleedTokens.test.ts` in
75
+ * @gryt/ui keeps it equal to what theme.css says. Two overhangs that must
76
+ * match is how they stop matching.
77
+ */
78
+ const bleed = grytDrawerBleed;
79
+ const panelExtent = extent + bleed;
80
+ // Measured against the bleed panel, so the panel's *visible* edge lands
81
+ // exactly off-screen rather than the overhang doing it.
82
+ const hidden = side === "right" ? panelExtent : side === "left" ? -panelExtent : panelExtent;
83
+ /**
84
+ * 700ms on the *tight* curve, and this changed on both platforms at once.
85
+ *
86
+ * It used to be the overshooting one, matching the web, with a note saying
87
+ * that a panel travelling its own width is exactly the case
88
+ * `--ease-spring-tight` was added for — and that if it ever felt wrong on a
89
+ * device, the web was what was wrong and both should change together.
90
+ *
91
+ * It felt wrong on a device. A full-height panel arriving with a 12%
92
+ * overshoot reads as aggressive rather than lively, because the overshoot is
93
+ * a percentage of the travel and the travel here is most of the screen. The
94
+ * same 12% on a 20px switch thumb is texture; on a 320pt panel it is a slam.
95
+ *
96
+ * `@gryt/ui`'s Drawer moved to `ease-spring-tight` in the same change.
97
+ */
98
+ /**
99
+ * Mounted for longer than it is open, so the panel can animate out.
100
+ *
101
+ * React Native's `Modal` unmounts the moment `visible` goes false, and the
102
+ * close was also snapping `progress` straight to 0 rather than animating it
103
+ * — so a dismissed drawer did not slide away, it simply stopped existing.
104
+ * Both halves had to change: animate the close, and stay mounted until that
105
+ * animation has finished.
106
+ */
107
+ const [mounted, setMounted] = useState(open);
108
+ useEffect(() => {
109
+ if (open)
110
+ setMounted(true);
111
+ }, [open]);
47
112
  useEffect(() => {
113
+ if (!mounted)
114
+ return;
115
+ if (reducedMotion) {
116
+ // eslint-disable-next-line react-hooks/immutability
117
+ progress.value = open ? 1 : 0;
118
+ if (!open)
119
+ setMounted(false);
120
+ return;
121
+ }
48
122
  // eslint-disable-next-line react-hooks/immutability
49
- progress.value =
50
- !open || reducedMotion
51
- ? open
52
- ? 1
53
- : 0
54
- : springy(1, { duration: durations.springSoft });
55
- }, [open, progress, reducedMotion]);
56
- const panelStyle = useAnimatedStyle(() => ({
57
- transform: [
58
- vertical
59
- ? { translateY: hidden + (0 - hidden) * progress.value }
60
- : { translateX: hidden + (0 - hidden) * progress.value }
61
- ],
62
- opacity: progress.value
123
+ progress.value = travelTo(open ? 1 : 0, { duration: durations.springSoft }, (finished) => {
124
+ "worklet";
125
+ // Unmount only once the panel is actually gone, and only if the
126
+ // animation ran to the end — an interrupted close means it was
127
+ // reopened, and unmounting then would take the drawer away as it
128
+ // arrives.
129
+ if (finished && !open)
130
+ runOnJS(setMounted)(false);
131
+ });
132
+ }, [open, mounted, progress, reducedMotion]);
133
+ /**
134
+ * How far the finger has dragged the panel away from open, in points.
135
+ *
136
+ * Separate from `progress` rather than folded into it. `progress` is
137
+ * animated on the spring, and a drag has to track the finger exactly —
138
+ * feeding a gesture through an overshooting curve makes the panel lead and
139
+ * lag the thumb, which reads as the drawer being slippery.
140
+ */
141
+ const drag = useSharedValue(0);
142
+ const panelStyle = useAnimatedStyle(() => {
143
+ const travel = hidden + (0 - hidden) * progress.value + drag.value;
144
+ return {
145
+ // Transform only. The panel slides; it does not fade. This used to carry
146
+ // `opacity: progress.value`, which the web's Popup does not — it declares
147
+ // `transition-transform` and nothing else — and a panel that fades as it
148
+ // arrives reads as a dialog rather than a drawer.
149
+ transform: [vertical ? { translateY: travel } : { translateX: travel }]
150
+ };
151
+ });
152
+ /**
153
+ * The scrim fades with the panel, and thins as it is dragged away.
154
+ *
155
+ * It was a flat `rgba(0,0,0,0.6)` that appeared and vanished with the Modal,
156
+ * so a drawer animating out sat under a full-strength scrim for the whole
157
+ * 700ms and then the scrim blinked off. The web has animated this from the
158
+ * start — `transition-opacity` on the same duration and curve — and it also
159
+ * tracks the swipe, with a comment saying not to "leave a full-strength
160
+ * scrim over a half-gone sheet".
161
+ *
162
+ * Both halves are here: `progress` fades it in and out, and the drag term
163
+ * thins it in proportion to how far the panel has been pushed off.
164
+ */
165
+ const scrimStyle = useAnimatedStyle(() => {
166
+ const dragged = extent > 0 ? Math.min(1, Math.abs(drag.value) / extent) : 0;
167
+ return { opacity: progress.value * (1 - dragged) };
168
+ });
169
+ /**
170
+ * Swipe to dismiss, which the web has had from Base UI's drawer primitive
171
+ * and this did not have at all (GRYT-395). A drawer you cannot push back is
172
+ * the thing a drawer is for.
173
+ *
174
+ * `PanResponder` rather than react-native-gesture-handler: the Slider proved
175
+ * this path works from this package's prebuilt output today, and an attempt
176
+ * at gesture-handler from here failed for reasons still not understood
177
+ * (GRYT-393). This is not the place to retry that.
178
+ */
179
+ const gesture = useRef({ extent, side, vertical, dismissible, setOpen });
180
+ useEffect(() => {
181
+ gesture.current = { extent, side, vertical, dismissible, setOpen };
182
+ }, [extent, side, vertical, dismissible, setOpen]);
183
+ /* eslint-disable react-hooks/refs */
184
+ const [responder] = useState(() => PanResponder.create({
185
+ // Claimed on move, not on start. Claiming on start would swallow every
186
+ // tap on the panel's own content — buttons inside a drawer would stop
187
+ // working, which is a far worse bug than not being able to swipe.
188
+ onStartShouldSetPanResponder: () => false,
189
+ onMoveShouldSetPanResponder: (_e, g) => {
190
+ if (!gesture.current.dismissible)
191
+ return false;
192
+ // Only once the drag is clearly along the axis the panel closes on,
193
+ // and clearly a drag rather than a slow press. Anything else belongs
194
+ // to whatever is inside.
195
+ const along = gesture.current.vertical ? g.dy : g.dx;
196
+ const across = gesture.current.vertical ? g.dx : g.dy;
197
+ return Math.abs(along) > 8 && Math.abs(along) > Math.abs(across);
198
+ },
199
+ // Once it is ours it stays ours, for the same reason the Slider needs
200
+ // this: the default is to grant, and a ScrollView inside the drawer
201
+ // would take the drag back mid-swipe.
202
+ onPanResponderTerminationRequest: () => false,
203
+ onPanResponderMove: (_e, g) => {
204
+ const { vertical: v, side: sd } = gesture.current;
205
+ const raw = v ? g.dy : g.dx;
206
+ // Clamped to the closing direction. Dragging a left drawer rightwards
207
+ // should do nothing, not tear it off its edge.
208
+ const closing = sd === "left" ? Math.min(0, raw) : Math.max(0, raw);
209
+ // eslint-disable-next-line react-hooks/immutability
210
+ drag.value = closing;
211
+ },
212
+ onPanResponderRelease: (_e, g) => {
213
+ const { vertical: v, side: sd, extent: ex } = gesture.current;
214
+ const moved = Math.abs(v ? g.dy : g.dx);
215
+ const speed = Math.abs(v ? g.vy : g.vx);
216
+ // Half the panel, or a flick. The velocity term is what makes a short
217
+ // sharp swipe work — without it you have to drag the whole way, which
218
+ // is the difference between a drawer and a slow puzzle.
219
+ const dismiss = moved > ex / 2 || speed > 0.5;
220
+ if (dismiss) {
221
+ gesture.current.setOpen(false);
222
+ // Left where it is: the close animation runs from here, and resetting
223
+ // it first would snap the panel back before it left.
224
+ return;
225
+ }
226
+ // eslint-disable-next-line react-hooks/immutability
227
+ drag.value = travelTo(0, { duration: durations.springSoft });
228
+ },
229
+ onPanResponderTerminate: () => {
230
+ // eslint-disable-next-line react-hooks/immutability
231
+ drag.value = travelTo(0, { duration: durations.springSoft });
232
+ }
63
233
  }));
64
- return (_jsx(Modal, { visible: open, transparent: true, animationType: "none", onRequestClose: dismissible ? () => setOpen(false) : undefined, children: _jsx(Pressable, { onPress: dismissible ? () => setOpen(false) : undefined, style: { flex: 1, backgroundColor: "rgba(0,0,0,0.6)" }, children: _jsx(Animated.View, { accessibilityViewIsModal: true, style: [
234
+ /* eslint-enable react-hooks/refs */
235
+ // Cleared once the panel is closed, so the next open does not start from
236
+ // wherever the last swipe left it.
237
+ useEffect(() => {
238
+ if (!open) {
239
+ // eslint-disable-next-line react-hooks/immutability
240
+ drag.value = 0;
241
+ }
242
+ }, [open, drag]);
243
+ return (_jsx(Modal, { visible: mounted, transparent: true, animationType: "none", onRequestClose: dismissible ? () => setOpen(false) : undefined, children: _jsx(AnimatedPressable, { onPress: dismissible ? () => setOpen(false) : undefined, style: [{ flex: 1, backgroundColor: "rgba(0,0,0,0.6)" }, scrimStyle], children: _jsx(Animated.View, { accessibilityViewIsModal: true, ...responder.panHandlers, style: [
65
244
  {
66
245
  position: "absolute",
246
+ // The overhang hangs *off* the edge the panel comes from, which
247
+ // is the only direction that helps. It used to be added to the
248
+ // width instead, so the panel grew inwards — 64pt more of the
249
+ // screen covered, and the seam it was meant to hide still there,
250
+ // because the gap opens on the entering edge and that is the edge
251
+ // the extra material was not on.
67
252
  top: side === "bottom" ? undefined : 0,
68
- bottom: 0,
69
- left: side === "right" ? undefined : 0,
70
- right: side === "left" ? undefined : 0,
71
- width: vertical ? "100%" : extent,
72
- height: vertical ? extent : "100%",
253
+ bottom: vertical ? -bleed : 0,
254
+ left: side === "right" ? undefined : side === "left" ? -bleed : 0,
255
+ right: side === "left" ? undefined : side === "right" ? -bleed : 0,
256
+ width: vertical ? "100%" : panelExtent,
257
+ height: vertical ? panelExtent : "100%",
258
+ // Puts the content back where it would have been without the
259
+ // overhang, so `size` still means what it says.
260
+ paddingLeft: side === "left" ? bleed : 0,
261
+ paddingRight: side === "right" ? bleed : 0,
262
+ paddingBottom: vertical ? bleed : 0,
73
263
  backgroundColor: theme.color.surface,
74
264
  borderColor: theme.color.border,
75
265
  borderRightWidth: side === "left" ? 1 : 0,
@@ -79,7 +269,16 @@ function Popup({ children, side = "left", size = 0.8, dismissible = true, style
79
269
  borderTopRightRadius: vertical ? theme.radius.lg : 0
80
270
  },
81
271
  panelStyle
82
- ], children: _jsx(Pressable, { onPress: () => { }, style: [{ flex: 1 }, style], children: children }) }) }) }));
272
+ ], children: _jsx(Pressable, { onPress: () => { }, style: [
273
+ {
274
+ flex: 1,
275
+ // Before the caller's style, so a drawer that wants to run
276
+ // under the island — a full-bleed image, say — still can.
277
+ paddingTop: vertical ? 0 : insets.top,
278
+ paddingBottom: insets.bottom
279
+ },
280
+ style
281
+ ], children: children }) }) }) }));
83
282
  }
84
283
  function Close({ children, style }) {
85
284
  const { setOpen } = useDrawer("Close");
@@ -6,24 +6,18 @@ export interface ProgressProps {
6
6
  style?: StyleProp<ViewStyle>;
7
7
  }
8
8
  /**
9
- * Determinate only and so is the web, which is not what this used to say.
9
+ * Determinate travel follows the web deliberately: 300ms on ease-out rather
10
+ * than the spring. A progress bar is a reading, and the spring's 12% overshoot
11
+ * on a jump to 100% runs past the end of the track and comes back — which reads
12
+ * as the job finishing, unfinishing, then finishing.
10
13
  *
11
- * The previous note here claimed the web's indeterminate state was a CSS
12
- * keyframe sweeping a bar across the track, and listed this as a parity
13
- * exception. It is not one. `@gryt/ui`'s Progress passes `value={null}` to
14
- * Base UI and styles nothing for it: there is no `@keyframes` anywhere in the
15
- * package, no rule for `data-indeterminate`, and the compiled stylesheet has
16
- * zero rules matching `gryt-progress`. An indeterminate Progress renders an
17
- * empty track on the web too.
18
- *
19
- * So both platforms agree, and both are missing the feature. Implementing it
20
- * belongs on both at once — GRYT-382 — rather than here, where it would make
21
- * React Native the one that behaves differently.
22
- *
23
- * The travel that *is* implemented follows the web deliberately: 300ms on
24
- * ease-out rather than the spring. A progress bar is a reading, and the
25
- * spring's 12% overshoot on a jump to 100% runs past the end of the track and
26
- * comes back — which reads as the job finishing, unfinishing, then finishing.
14
+ * Indeterminate is a partial bar sweeping the track on a loop, at
15
+ * `grytDurations.sweep`, from the same description the web's keyframe follows.
16
+ * Both were empty until GRYT-382: the web passed `value={null}` to Base UI,
17
+ * which leaves the indicator's width unset, and nothing styled it. This file
18
+ * previously called that a parity exception, which it was not — both platforms
19
+ * were missing the same feature, and writing it down as one-sided is what
20
+ * closed the question for a while.
27
21
  */
28
22
  export declare function Progress({ value, tone, style }: ProgressProps): import("react").JSX.Element;
29
23
  //# sourceMappingURL=Progress.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Progress.d.ts","sourceRoot":"","sources":["../../../src/components/Progress/Progress.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAIpE,MAAM,WAAW,aAAa;IAC5B,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;IACnD,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAe,EAAE,KAAK,EAAE,EAAE,aAAa,+BA4BxE"}
1
+ {"version":3,"file":"Progress.d.ts","sourceRoot":"","sources":["../../../src/components/Progress/Progress.tsx"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAatB,MAAM,WAAW,aAAa;IAC5B,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;IACnD,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAe,EAAE,KAAK,EAAE,EAAE,aAAa,+BA0FxE"}