@medway-ui/native 1.7.2 → 1.8.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 (50) hide show
  1. package/README.md +4 -1
  2. package/lib/commonjs/components/Badge.js +159 -36
  3. package/lib/commonjs/components/Badge.js.map +1 -1
  4. package/lib/commonjs/components/Choice.js +35 -37
  5. package/lib/commonjs/components/Choice.js.map +1 -1
  6. package/lib/commonjs/components/SegmentControl.js +197 -0
  7. package/lib/commonjs/components/SegmentControl.js.map +1 -0
  8. package/lib/commonjs/editor/editorHtml.generated.js +1 -1
  9. package/lib/commonjs/editor/editorHtml.generated.js.map +1 -1
  10. package/lib/commonjs/hooks/useThemeColor.js +42 -42
  11. package/lib/commonjs/hooks/useThemeColor.js.map +1 -1
  12. package/lib/commonjs/index.js +31 -0
  13. package/lib/commonjs/index.js.map +1 -1
  14. package/lib/commonjs/lib/raised-surface.js +24 -0
  15. package/lib/commonjs/lib/raised-surface.js.map +1 -0
  16. package/lib/module/components/Badge.js +159 -36
  17. package/lib/module/components/Badge.js.map +1 -1
  18. package/lib/module/components/Choice.js +35 -37
  19. package/lib/module/components/Choice.js.map +1 -1
  20. package/lib/module/components/SegmentControl.js +189 -0
  21. package/lib/module/components/SegmentControl.js.map +1 -0
  22. package/lib/module/editor/editorHtml.generated.js +1 -1
  23. package/lib/module/editor/editorHtml.generated.js.map +1 -1
  24. package/lib/module/hooks/useThemeColor.js +42 -42
  25. package/lib/module/hooks/useThemeColor.js.map +1 -1
  26. package/lib/module/index.js +2 -0
  27. package/lib/module/index.js.map +1 -1
  28. package/lib/module/lib/raised-surface.js +22 -0
  29. package/lib/module/lib/raised-surface.js.map +1 -0
  30. package/lib/typescript/components/Badge.d.ts +14 -4
  31. package/lib/typescript/components/Badge.d.ts.map +1 -1
  32. package/lib/typescript/components/Chip.d.ts +2 -2
  33. package/lib/typescript/components/Choice.d.ts.map +1 -1
  34. package/lib/typescript/components/SegmentControl.d.ts +37 -0
  35. package/lib/typescript/components/SegmentControl.d.ts.map +1 -0
  36. package/lib/typescript/components/Tag.d.ts +2 -2
  37. package/lib/typescript/editor/editorHtml.generated.d.ts.map +1 -1
  38. package/lib/typescript/index.d.ts +3 -1
  39. package/lib/typescript/index.d.ts.map +1 -1
  40. package/lib/typescript/lib/raised-surface.d.ts +3 -0
  41. package/lib/typescript/lib/raised-surface.d.ts.map +1 -0
  42. package/package.json +1 -1
  43. package/setup/tokens.native.css +49 -22
  44. package/src/components/Badge.tsx +207 -61
  45. package/src/components/Choice.tsx +66 -45
  46. package/src/components/SegmentControl.tsx +265 -0
  47. package/src/editor/editorHtml.generated.ts +1 -1
  48. package/src/hooks/useThemeColor.ts +42 -42
  49. package/src/index.ts +21 -1
  50. package/src/lib/raised-surface.tsx +21 -0
@@ -0,0 +1,265 @@
1
+ import {
2
+ Children,
3
+ createContext,
4
+ useContext,
5
+ useMemo,
6
+ useState,
7
+ type ReactNode,
8
+ } from "react";
9
+ import { Pressable, View, type Insets } from "react-native";
10
+ import * as TabsPrimitive from "@rn-primitives/tabs";
11
+ import { cva } from "class-variance-authority";
12
+
13
+ import { Badge, type BadgeProps } from "@/components/Badge";
14
+ import { Text } from "@/components/Text";
15
+ import { RaisedSurface } from "@/lib/raised-surface";
16
+ import { cn } from "@/lib/utils";
17
+
18
+ type SegmentSize = "sm" | "md" | "lg";
19
+
20
+ const SegmentControlContext = createContext<{ size: SegmentSize }>({
21
+ size: "md",
22
+ });
23
+
24
+ // `min-h` instead of the web's `max-h`: a larger system font grows the segment instead of clipping it.
25
+ const segmentControlTriggerVariants = cva(
26
+ "shrink flex-row items-center justify-center",
27
+ {
28
+ variants: {
29
+ size: {
30
+ sm: "min-h-7 gap-2 px-3 py-1.5",
31
+ md: "min-h-9 gap-2.5 px-3.5 py-2",
32
+ lg: "min-h-11 gap-3 px-4 py-2.5",
33
+ },
34
+ },
35
+ defaultVariants: { size: "md" },
36
+ }
37
+ );
38
+
39
+ // Up to the package's 44pt touch target (`MIN_TOUCH_TARGET`), vertically only: segments sit 2px apart.
40
+ const TRIGGER_HIT_SLOP: Record<SegmentSize, Insets | undefined> = {
41
+ sm: { top: 8, bottom: 8 },
42
+ md: { top: 4, bottom: 4 },
43
+ lg: undefined,
44
+ };
45
+
46
+ const segmentControlTextVariants = cva("text-center", {
47
+ variants: {
48
+ size: {
49
+ sm: "text-xs leading-xs",
50
+ md: "text-sm leading-sm",
51
+ lg: "text-md leading-md",
52
+ },
53
+ },
54
+ defaultVariants: { size: "md" },
55
+ });
56
+
57
+ interface SegmentControlProps
58
+ extends Omit<TabsPrimitive.RootProps, "value" | "onValueChange"> {
59
+ value?: string;
60
+ defaultValue?: string;
61
+ onValueChange?: (value: string) => void;
62
+ size?: SegmentSize;
63
+ className?: string;
64
+ children?: ReactNode;
65
+ }
66
+
67
+ function SegmentControl({
68
+ value: valueProp,
69
+ defaultValue = "",
70
+ onValueChange,
71
+ size = "md",
72
+ className,
73
+ children,
74
+ ...props
75
+ }: SegmentControlProps) {
76
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
77
+ const value = valueProp ?? uncontrolledValue;
78
+ const context = useMemo(() => ({ size }), [size]);
79
+
80
+ const handleValueChange = (next: string) => {
81
+ if (next === value) return;
82
+ if (valueProp === undefined) setUncontrolledValue(next);
83
+ onValueChange?.(next);
84
+ };
85
+
86
+ return (
87
+ <SegmentControlContext.Provider value={context}>
88
+ <TabsPrimitive.Root
89
+ asChild
90
+ value={value}
91
+ onValueChange={handleValueChange}
92
+ {...props}
93
+ >
94
+ <View className={cn("gap-2", className)}>{children}</View>
95
+ </TabsPrimitive.Root>
96
+ </SegmentControlContext.Provider>
97
+ );
98
+ }
99
+
100
+ interface SegmentControlListProps extends TabsPrimitive.ListProps {
101
+ className?: string;
102
+ children?: ReactNode;
103
+ }
104
+
105
+ function SegmentControlList({
106
+ className,
107
+ children,
108
+ ...props
109
+ }: SegmentControlListProps) {
110
+ return (
111
+ <TabsPrimitive.List asChild {...props}>
112
+ <View
113
+ className={cn(
114
+ "flex-row items-center gap-0.5 self-start rounded-md bg-gray-background-soft p-0.5",
115
+ className
116
+ )}
117
+ >
118
+ {children}
119
+ </View>
120
+ </TabsPrimitive.List>
121
+ );
122
+ }
123
+
124
+ interface SegmentControlTriggerProps extends TabsPrimitive.TriggerProps {
125
+ className?: string;
126
+ children?: ReactNode;
127
+ icon?: ReactNode;
128
+ badge?: number | string;
129
+ badgeVariant?: BadgeProps["variant"];
130
+ }
131
+
132
+ function shouldShowBadge(
133
+ badge: number | string | undefined,
134
+ disabled: boolean
135
+ ): badge is number | string {
136
+ return (
137
+ badge != null && badge !== 0 && badge !== "" && badge !== "0" && !disabled
138
+ );
139
+ }
140
+
141
+ // Strings and numbers, as in `Aulas {count}`, join into one label; anything else renders as is.
142
+ function toLabel(children: ReactNode): string | null {
143
+ const parts = Children.toArray(children);
144
+ return parts.length > 0 &&
145
+ parts.every((part) => typeof part === "string" || typeof part === "number")
146
+ ? parts.join("")
147
+ : null;
148
+ }
149
+
150
+ function SegmentControlTrigger({
151
+ className,
152
+ children,
153
+ icon,
154
+ badge,
155
+ badgeVariant,
156
+ value,
157
+ disabled,
158
+ accessibilityLabel,
159
+ ...props
160
+ }: SegmentControlTriggerProps) {
161
+ const { size } = useContext(SegmentControlContext);
162
+ const { value: activeValue } = TabsPrimitive.useRootContext();
163
+ const isActive = activeValue === value;
164
+ const isDisabled = !!disabled;
165
+ const showBadge = shouldShowBadge(badge, isDisabled);
166
+ const label = toLabel(children);
167
+
168
+ return (
169
+ <TabsPrimitive.Trigger asChild value={value} disabled={disabled} {...props}>
170
+ <Pressable
171
+ accessibilityLabel={
172
+ accessibilityLabel ??
173
+ (label != null
174
+ ? showBadge
175
+ ? `${label}, ${badge}`
176
+ : label
177
+ : undefined)
178
+ }
179
+ hitSlop={TRIGGER_HIT_SLOP[size]}
180
+ className={cn(segmentControlTriggerVariants({ size }), className)}
181
+ >
182
+ {({ pressed }) => (
183
+ <>
184
+ {isActive ? <RaisedSurface /> : null}
185
+ {icon ? <View className="shrink-0">{icon}</View> : null}
186
+ {label != null ? (
187
+ <View className="min-w-0 shrink">
188
+ {/* Holds the semibold width, so the row doesn't shift when the active segment changes. */}
189
+ <Text
190
+ numberOfLines={1}
191
+ accessibilityElementsHidden
192
+ importantForAccessibility="no-hide-descendants"
193
+ className={cn(
194
+ segmentControlTextVariants({ size }),
195
+ "font-semibold opacity-0"
196
+ )}
197
+ >
198
+ {label}
199
+ </Text>
200
+ <Text
201
+ numberOfLines={1}
202
+ className={cn(
203
+ segmentControlTextVariants({ size }),
204
+ "absolute inset-0",
205
+ isActive ? "font-semibold" : "font-medium",
206
+ isDisabled
207
+ ? "text-gray-foreground-muted"
208
+ : isActive
209
+ ? pressed
210
+ ? "text-primary-foreground-emphasis"
211
+ : "text-primary-foreground"
212
+ : pressed
213
+ ? "text-gray-foreground-emphasis"
214
+ : "text-gray-foreground"
215
+ )}
216
+ >
217
+ {label}
218
+ </Text>
219
+ </View>
220
+ ) : (
221
+ children
222
+ )}
223
+ {showBadge ? (
224
+ <Badge size={size} variant={badgeVariant}>
225
+ {String(badge)}
226
+ </Badge>
227
+ ) : null}
228
+ </>
229
+ )}
230
+ </Pressable>
231
+ </TabsPrimitive.Trigger>
232
+ );
233
+ }
234
+
235
+ interface SegmentControlContentProps extends TabsPrimitive.ContentProps {
236
+ className?: string;
237
+ children?: ReactNode;
238
+ }
239
+
240
+ function SegmentControlContent({
241
+ className,
242
+ children,
243
+ ...props
244
+ }: SegmentControlContentProps) {
245
+ return (
246
+ <TabsPrimitive.Content asChild {...props}>
247
+ <View className={cn("flex-1", className)}>{children}</View>
248
+ </TabsPrimitive.Content>
249
+ );
250
+ }
251
+
252
+ export {
253
+ SegmentControl,
254
+ SegmentControlContent,
255
+ SegmentControlList,
256
+ SegmentControlTrigger,
257
+ segmentControlTriggerVariants,
258
+ };
259
+ export type {
260
+ SegmentControlContentProps,
261
+ SegmentControlListProps,
262
+ SegmentControlProps,
263
+ SegmentControlTriggerProps,
264
+ SegmentSize,
265
+ };