panelui-native 0.9.0 → 0.12.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 (105) hide show
  1. package/README.md +5 -3
  2. package/lib/module/components/bottom-sheet/index.js +38 -2
  3. package/lib/module/components/bottom-sheet/index.js.map +1 -1
  4. package/lib/module/components/button/index.js +19 -1
  5. package/lib/module/components/button/index.js.map +1 -1
  6. package/lib/module/components/frame/index.js +128 -15
  7. package/lib/module/components/frame/index.js.map +1 -1
  8. package/lib/module/components/input/index.js +107 -15
  9. package/lib/module/components/input/index.js.map +1 -1
  10. package/lib/module/components/popover/index.js +22 -11
  11. package/lib/module/components/popover/index.js.map +1 -1
  12. package/lib/module/components/progress/index.js +52 -2
  13. package/lib/module/components/progress/index.js.map +1 -1
  14. package/lib/module/components/radio-group/index.js +34 -7
  15. package/lib/module/components/radio-group/index.js.map +1 -1
  16. package/lib/module/components/section-rail/index.js +355 -0
  17. package/lib/module/components/section-rail/index.js.map +1 -0
  18. package/lib/module/components/select/index.js +245 -56
  19. package/lib/module/components/select/index.js.map +1 -1
  20. package/lib/module/components/slider/index.js +355 -0
  21. package/lib/module/components/slider/index.js.map +1 -0
  22. package/lib/module/components/switch/index.js +10 -1
  23. package/lib/module/components/switch/index.js.map +1 -1
  24. package/lib/module/components/tabs/index.js +186 -16
  25. package/lib/module/components/tabs/index.js.map +1 -1
  26. package/lib/module/components/toggle-button/index.js +297 -0
  27. package/lib/module/components/toggle-button/index.js.map +1 -0
  28. package/lib/module/hooks/index.js +1 -0
  29. package/lib/module/hooks/index.js.map +1 -1
  30. package/lib/module/hooks/use-keyboard-avoidance.js +57 -8
  31. package/lib/module/hooks/use-keyboard-avoidance.js.map +1 -1
  32. package/lib/module/hooks/use-scroll-sections.js +89 -0
  33. package/lib/module/hooks/use-scroll-sections.js.map +1 -0
  34. package/lib/module/index.js +4 -1
  35. package/lib/module/index.js.map +1 -1
  36. package/lib/module/native/index.js.map +1 -1
  37. package/lib/module/primitives/keyboard-avoider.js +2 -0
  38. package/lib/module/primitives/keyboard-avoider.js.map +1 -1
  39. package/lib/module/utils/haptics.js +57 -0
  40. package/lib/module/utils/haptics.js.map +1 -0
  41. package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
  42. package/lib/typescript/src/components/button/index.d.ts +9 -4
  43. package/lib/typescript/src/components/button/index.d.ts.map +1 -1
  44. package/lib/typescript/src/components/frame/index.d.ts +47 -7
  45. package/lib/typescript/src/components/frame/index.d.ts.map +1 -1
  46. package/lib/typescript/src/components/input/index.d.ts +113 -2
  47. package/lib/typescript/src/components/input/index.d.ts.map +1 -1
  48. package/lib/typescript/src/components/popover/index.d.ts +7 -1
  49. package/lib/typescript/src/components/popover/index.d.ts.map +1 -1
  50. package/lib/typescript/src/components/progress/index.d.ts +33 -0
  51. package/lib/typescript/src/components/progress/index.d.ts.map +1 -1
  52. package/lib/typescript/src/components/radio-group/index.d.ts +7 -0
  53. package/lib/typescript/src/components/radio-group/index.d.ts.map +1 -1
  54. package/lib/typescript/src/components/section-rail/index.d.ts +115 -0
  55. package/lib/typescript/src/components/section-rail/index.d.ts.map +1 -0
  56. package/lib/typescript/src/components/select/index.d.ts +46 -7
  57. package/lib/typescript/src/components/select/index.d.ts.map +1 -1
  58. package/lib/typescript/src/components/slider/index.d.ts +203 -0
  59. package/lib/typescript/src/components/slider/index.d.ts.map +1 -0
  60. package/lib/typescript/src/components/switch/index.d.ts.map +1 -1
  61. package/lib/typescript/src/components/tabs/index.d.ts +46 -4
  62. package/lib/typescript/src/components/tabs/index.d.ts.map +1 -1
  63. package/lib/typescript/src/components/toggle-button/index.d.ts +244 -0
  64. package/lib/typescript/src/components/toggle-button/index.d.ts.map +1 -0
  65. package/lib/typescript/src/hooks/index.d.ts +1 -0
  66. package/lib/typescript/src/hooks/index.d.ts.map +1 -1
  67. package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts +10 -1
  68. package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts.map +1 -1
  69. package/lib/typescript/src/hooks/use-scroll-sections.d.ts +31 -0
  70. package/lib/typescript/src/hooks/use-scroll-sections.d.ts.map +1 -0
  71. package/lib/typescript/src/index.d.ts +6 -3
  72. package/lib/typescript/src/index.d.ts.map +1 -1
  73. package/lib/typescript/src/native/index.d.ts +16 -1
  74. package/lib/typescript/src/native/index.d.ts.map +1 -1
  75. package/lib/typescript/src/primitives/keyboard-avoider.d.ts +8 -1
  76. package/lib/typescript/src/primitives/keyboard-avoider.d.ts.map +1 -1
  77. package/lib/typescript/src/utils/haptics.d.ts +12 -0
  78. package/lib/typescript/src/utils/haptics.d.ts.map +1 -0
  79. package/package.json +5 -1
  80. package/src/components/bottom-sheet/index.tsx +38 -2
  81. package/src/components/button/index.tsx +26 -5
  82. package/src/components/frame/index.tsx +175 -29
  83. package/src/components/input/index.tsx +122 -16
  84. package/src/components/popover/index.tsx +49 -24
  85. package/src/components/progress/index.tsx +81 -1
  86. package/src/components/radio-group/index.tsx +55 -8
  87. package/src/components/section-rail/index.tsx +459 -0
  88. package/src/components/select/index.tsx +265 -52
  89. package/src/components/slider/index.tsx +371 -0
  90. package/src/components/switch/index.tsx +8 -1
  91. package/src/components/tabs/index.tsx +208 -28
  92. package/src/components/toggle-button/index.tsx +374 -0
  93. package/src/hooks/index.ts +5 -0
  94. package/src/hooks/use-keyboard-avoidance.ts +73 -7
  95. package/src/hooks/use-scroll-sections.ts +127 -0
  96. package/src/index.ts +37 -6
  97. package/src/native/index.ts +13 -1
  98. package/src/primitives/keyboard-avoider.tsx +9 -1
  99. package/src/utils/haptics.ts +63 -0
  100. package/theme.css +6 -2
  101. package/lib/module/components/inline-select/index.js +0 -284
  102. package/lib/module/components/inline-select/index.js.map +0 -1
  103. package/lib/typescript/src/components/inline-select/index.d.ts +0 -48
  104. package/lib/typescript/src/components/inline-select/index.d.ts.map +0 -1
  105. package/src/components/inline-select/index.tsx +0 -332
@@ -11,6 +11,8 @@ import Animated, {
11
11
  withTiming,
12
12
  } from 'react-native-reanimated';
13
13
  import { tv, type VariantProps } from 'tailwind-variants';
14
+ import { Text } from '../../primitives/text';
15
+ import { cn } from '../../utils/cn';
14
16
 
15
17
  const SPRING = { damping: 20, stiffness: 180, mass: 0.6 } as const;
16
18
  /** Fraction of the track covered by the sliding bar in indeterminate mode. */
@@ -18,6 +20,8 @@ const INDETERMINATE_WIDTH = 0.4;
18
20
 
19
21
  const progressVariants = tv({
20
22
  slots: {
23
+ root: 'w-full gap-2',
24
+ header: 'flex-row items-center justify-between',
21
25
  track: 'w-full overflow-hidden rounded-full',
22
26
  indicator: 'h-full rounded-full',
23
27
  },
@@ -53,6 +57,30 @@ export interface ProgressProps
53
57
  indeterminate?: boolean;
54
58
  /** Extra classes for the moving indicator. */
55
59
  indicatorClassName?: string;
60
+ /**
61
+ * Caption drawn above the track, on the left. Supplying it (or
62
+ * `showValueLabel`) wraps the bar in a header row; the track alone renders
63
+ * otherwise.
64
+ */
65
+ label?: string;
66
+ /**
67
+ * Draw the percentage above the track, on the right. Hidden while
68
+ * `indeterminate` — there is nothing meaningful to show.
69
+ */
70
+ showValueLabel?: boolean;
71
+ /**
72
+ * Text for the value label. Overrides the formatted percentage — use it for
73
+ * a byte count, a step tally, anything that is not a bare percent.
74
+ */
75
+ valueLabel?: string;
76
+ /**
77
+ * Format the value label as a fraction of the whole with `Intl.NumberFormat`
78
+ * — e.g. `{ style: 'currency', currency: 'USD' }`. Falls back to a rounded
79
+ * percent when omitted.
80
+ */
81
+ formatOptions?: Intl.NumberFormatOptions;
82
+ /** Extra classes for the label + value-label row. */
83
+ headerClassName?: string;
56
84
  }
57
85
 
58
86
  function clamp(value: number) {
@@ -62,18 +90,43 @@ function clamp(value: number) {
62
90
  return value;
63
91
  }
64
92
 
93
+ /** The right-hand caption: an explicit override, an Intl fraction, or a percent. */
94
+ function formatValue(
95
+ value: number,
96
+ valueLabel?: string,
97
+ formatOptions?: Intl.NumberFormatOptions
98
+ ) {
99
+ if (valueLabel != null) return valueLabel;
100
+ if (formatOptions) {
101
+ try {
102
+ return new Intl.NumberFormat(undefined, formatOptions).format(value / 100);
103
+ } catch {
104
+ // Some engines ship a partial Intl; fall through to the plain percent.
105
+ }
106
+ }
107
+ return `${Math.round(value)}%`;
108
+ }
109
+
65
110
  /**
66
111
  * Determinate or indeterminate progress bar. The fill width (determinate) and
67
112
  * the sliding bar (indeterminate) are both driven on the UI thread, so updates
68
113
  * never re-render past the value change itself.
114
+ *
115
+ * Pass `label` or `showValueLabel` to caption the bar with a header row; the
116
+ * bare track renders when neither is set, so existing call sites are untouched.
69
117
  */
70
118
  export const Progress = forwardRef<View, ProgressProps>(
71
119
  (
72
120
  {
73
121
  className,
74
122
  indicatorClassName,
123
+ headerClassName,
75
124
  value = 0,
76
125
  indeterminate = false,
126
+ label,
127
+ showValueLabel = false,
128
+ valueLabel,
129
+ formatOptions,
77
130
  color,
78
131
  size,
79
132
  ...props
@@ -124,10 +177,15 @@ export const Progress = forwardRef<View, ProgressProps>(
124
177
  };
125
178
  });
126
179
 
127
- return (
180
+ // The value label is meaningless while looping, so it is dropped there.
181
+ const showValue = showValueLabel && !indeterminate;
182
+ const hasHeader = label != null || showValue;
183
+
184
+ const track = (
128
185
  <View
129
186
  ref={ref}
130
187
  accessibilityRole="progressbar"
188
+ accessibilityLabel={label}
131
189
  accessibilityValue={
132
190
  indeterminate ? undefined : { min: 0, max: 100, now: clamp(value) }
133
191
  }
@@ -141,6 +199,28 @@ export const Progress = forwardRef<View, ProgressProps>(
141
199
  />
142
200
  </View>
143
201
  );
202
+
203
+ if (!hasHeader) return track;
204
+
205
+ return (
206
+ <View className={slots.root()}>
207
+ <View className={slots.header({ className: headerClassName })}>
208
+ {label != null ? (
209
+ <Text size="sm" weight="medium" numberOfLines={1}>
210
+ {label}
211
+ </Text>
212
+ ) : (
213
+ <View />
214
+ )}
215
+ {showValue ? (
216
+ <Text size="sm" muted className={cn(label == null && 'ml-auto')}>
217
+ {formatValue(clamp(value), valueLabel, formatOptions)}
218
+ </Text>
219
+ ) : null}
220
+ </View>
221
+ {track}
222
+ </View>
223
+ );
144
224
  }
145
225
  );
146
226
 
@@ -38,10 +38,14 @@ import { cn } from '../../utils/cn';
38
38
  import { Text } from '../../primitives/text';
39
39
 
40
40
  type RadioVariant = 'dot' | 'card';
41
+ type RadioOrientation = 'vertical' | 'horizontal';
41
42
 
42
43
  const itemVariants = tv({
43
44
  slots: {
44
- row: 'flex-row items-center gap-2.5',
45
+ // `self-start` matters: the group is a column, so without it Yoga stretches
46
+ // the row to the full width and the dead space to the right of the label
47
+ // becomes part of the target. Tapping nothing would select the option.
48
+ row: 'flex-row items-center gap-2.5 self-start',
45
49
  indicator:
46
50
  'h-5 w-5 items-center justify-center rounded-full border border-input bg-background',
47
51
  label: 'text-sm text-foreground',
@@ -52,8 +56,9 @@ const itemVariants = tv({
52
56
  dot: {},
53
57
  card: {
54
58
  // The whole surface is the target; the disc moves to the trailing edge
55
- // as a confirmation of the row, not the affordance for it.
56
- row: 'w-full items-start justify-between gap-3 rounded-xl border border-border bg-card p-4',
59
+ // as a confirmation of the row, not the affordance for it. Here the
60
+ // full width is deliberate, so it takes `self-stretch` back.
61
+ row: 'w-full self-stretch items-start justify-between gap-3 rounded-xl border border-border bg-card p-4',
57
62
  label: 'text-base font-medium',
58
63
  },
59
64
  },
@@ -63,9 +68,19 @@ const itemVariants = tv({
63
68
  disabled: {
64
69
  true: { row: 'opacity-50' },
65
70
  },
71
+ /**
72
+ * A card in a row shares the width rather than filling it, so two or three
73
+ * options sit side by side instead of each taking the whole line.
74
+ */
75
+ horizontal: {
76
+ true: {},
77
+ },
66
78
  },
67
79
  compoundVariants: [
68
80
  { variant: 'card', selected: true, class: { row: 'border-primary bg-accent' } },
81
+ // Share the row instead of filling it, keeping the disc on the trailing
82
+ // edge of each narrower card.
83
+ { variant: 'card', horizontal: true, class: { row: 'w-auto flex-1' } },
69
84
  ],
70
85
  defaultVariants: {
71
86
  variant: 'dot',
@@ -77,6 +92,7 @@ interface RadioGroupContextValue {
77
92
  onValueChange: (value: string) => void;
78
93
  disabled?: boolean;
79
94
  variant: RadioVariant;
95
+ orientation: RadioOrientation;
80
96
  }
81
97
 
82
98
  const RadioGroupContext = createContext<RadioGroupContextValue | null>(null);
@@ -92,14 +108,32 @@ export interface RadioGroupProps extends ViewProps {
92
108
  * settings choice where each option carries a description.
93
109
  */
94
110
  variant?: RadioVariant;
111
+ /**
112
+ * `horizontal` lays the options out along a row that wraps — for two or
113
+ * three short choices, where a stacked list wastes the width and reads as
114
+ * longer than it is.
115
+ */
116
+ orientation?: RadioOrientation;
95
117
  children: ReactNode;
96
118
  }
97
119
 
98
120
  const RadioGroupRoot = forwardRef<View, RadioGroupProps>(
99
- ({ className, value, onValueChange, disabled, variant = 'dot', children, ...props }, ref) => {
121
+ (
122
+ {
123
+ className,
124
+ value,
125
+ onValueChange,
126
+ disabled,
127
+ variant = 'dot',
128
+ orientation = 'vertical',
129
+ children,
130
+ ...props
131
+ },
132
+ ref
133
+ ) => {
100
134
  const context = useMemo(
101
- () => ({ value, onValueChange, disabled, variant }),
102
- [value, onValueChange, disabled, variant]
135
+ () => ({ value, onValueChange, disabled, variant, orientation }),
136
+ [value, onValueChange, disabled, variant, orientation]
103
137
  );
104
138
 
105
139
  return (
@@ -107,7 +141,15 @@ const RadioGroupRoot = forwardRef<View, RadioGroupProps>(
107
141
  <View
108
142
  ref={ref}
109
143
  accessibilityRole="radiogroup"
110
- className={cn('gap-3', className)}
144
+ className={cn(
145
+ orientation === 'horizontal'
146
+ ? // Wrapping, not scrolling: a choice that runs off the edge is
147
+ // a choice nobody knows is there. The cross-axis gap is
148
+ // smaller because wrapped rows already read as separate.
149
+ 'flex-row flex-wrap items-stretch gap-x-5 gap-y-3'
150
+ : 'gap-3',
151
+ className
152
+ )}
111
153
  {...props}
112
154
  >
113
155
  {children}
@@ -156,7 +198,12 @@ const RadioGroupItem = forwardRef<View, RadioGroupItemProps>(
156
198
  transform: [{ scale: progress.value }],
157
199
  }));
158
200
 
159
- const slots = itemVariants({ variant, selected, disabled: !!disabled });
201
+ const slots = itemVariants({
202
+ variant,
203
+ selected,
204
+ disabled: !!disabled,
205
+ horizontal: context.orientation === 'horizontal',
206
+ });
160
207
 
161
208
  const indicator = hideIndicator ? null : (
162
209
  <View className={slots.indicator()}>