@vellira-ui/react-native 2.52.0 → 2.54.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 +49 -110
- package/dist/index.d.ts +2 -0
- package/dist/index.js +518 -70
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,41 +1,24 @@
|
|
|
1
1
|
# @vellira-ui/react-native
|
|
2
2
|
|
|
3
|
-
React Native
|
|
3
|
+
React Native implementation of the Vellira design system.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- Button
|
|
10
|
-
- Checkbox
|
|
11
|
-
- Input
|
|
12
|
-
- FormField
|
|
13
|
-
- Radio
|
|
14
|
-
- RadioGroup
|
|
15
|
-
- Select
|
|
16
|
-
- Dropdown
|
|
17
|
-
- Tabs
|
|
18
|
-
- Tooltip
|
|
19
|
-
- Modal
|
|
20
|
-
|
|
21
|
-
Each public native component has React Native Storybook coverage and Vitest unit coverage.
|
|
22
|
-
|
|
23
|
-
For detailed props, shared types, examples, and compound component APIs, see
|
|
24
|
-
[Native Component API](./API.md).
|
|
5
|
+
It extends shared contracts from `@vellira-ui/types`, uses shared tokens from
|
|
6
|
+
`@vellira-ui/tokens`, shares renderer-neutral behavior through
|
|
7
|
+
`@vellira-ui/core`, and keeps native rendering, accessibility, layout, and
|
|
8
|
+
interaction behavior inside the React Native layer.
|
|
25
9
|
|
|
26
10
|
## Installation
|
|
27
11
|
|
|
12
|
+
Requires React and React Native.
|
|
13
|
+
|
|
28
14
|
```bash
|
|
29
15
|
pnpm add @vellira-ui/react-native
|
|
30
16
|
```
|
|
31
17
|
|
|
32
|
-
Peer dependencies:
|
|
33
|
-
|
|
34
|
-
- `react`
|
|
35
|
-
- `react-native`
|
|
36
|
-
|
|
37
18
|
## Usage
|
|
38
19
|
|
|
20
|
+
Use the components directly in your React Native application:
|
|
21
|
+
|
|
39
22
|
```tsx
|
|
40
23
|
import { Button, Checkbox, Input } from '@vellira-ui/react-native';
|
|
41
24
|
import { useState } from 'react';
|
|
@@ -47,7 +30,12 @@ export function Example() {
|
|
|
47
30
|
|
|
48
31
|
return (
|
|
49
32
|
<View style={{ gap: 16 }}>
|
|
50
|
-
<Input
|
|
33
|
+
<Input
|
|
34
|
+
label='Email'
|
|
35
|
+
value={email}
|
|
36
|
+
onValueChange={setEmail}
|
|
37
|
+
placeholder='name@example.com'
|
|
38
|
+
/>
|
|
51
39
|
<Checkbox
|
|
52
40
|
label='Accept terms'
|
|
53
41
|
description='Required to create an account.'
|
|
@@ -62,105 +50,56 @@ export function Example() {
|
|
|
62
50
|
}
|
|
63
51
|
```
|
|
64
52
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Use `accessibilityLabel` for icon-only native buttons:
|
|
68
|
-
|
|
69
|
-
```tsx
|
|
70
|
-
import { Search } from '@vellira-ui/icons';
|
|
71
|
-
|
|
72
|
-
<Button accessibilityLabel='Search' iconOnly iconStart={<Search />} />;
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Button icons are React elements from `@vellira-ui/icons`; the component injects
|
|
76
|
-
the current icon color and size. `loading` disables interaction and can replace
|
|
77
|
-
the visible label with `loadingText`.
|
|
78
|
-
|
|
79
|
-
### Checkbox Notes
|
|
80
|
-
|
|
81
|
-
Use `description` for settings-style helper text when the checkbox is not
|
|
82
|
-
wrapped in `FormField`. For checkbox rows without a visible label, provide
|
|
83
|
-
`accessibilityLabel`.
|
|
84
|
-
|
|
85
|
-
### Select Notes
|
|
53
|
+
## Components
|
|
86
54
|
|
|
87
|
-
|
|
88
|
-
visible choices, and `Dropdown` for action menus. Native `Select` opens a
|
|
89
|
-
sheet, modal, or popover and renders options with a FlatList-backed native
|
|
90
|
-
content surface.
|
|
55
|
+
### Inputs
|
|
91
56
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
57
|
+
- Button
|
|
58
|
+
- Checkbox
|
|
59
|
+
- Input
|
|
60
|
+
- Radio
|
|
61
|
+
- RadioGroup
|
|
62
|
+
- Select
|
|
95
63
|
|
|
96
|
-
|
|
97
|
-
const [role, setRole] = useState('editor');
|
|
64
|
+
### Overlays
|
|
98
65
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
onValueChange={(nextRole) => setRole(nextRole ?? '')}
|
|
104
|
-
searchable
|
|
105
|
-
clearable
|
|
106
|
-
>
|
|
107
|
-
<Select.Item value='admin' label='Admin' />
|
|
108
|
-
<Select.Item value='editor' label='Editor' />
|
|
109
|
-
<Select.Item value='viewer' label='Viewer' />
|
|
110
|
-
</Select>
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Dropdown Notes
|
|
66
|
+
- Dropdown
|
|
67
|
+
- Tooltip
|
|
68
|
+
- Popover
|
|
69
|
+
- Modal
|
|
116
70
|
|
|
117
|
-
|
|
118
|
-
menus with `Dropdown.Trigger`, `Dropdown.Content`, `Dropdown.Item`, groups,
|
|
119
|
-
labels, and separators. Use `open`, `defaultOpen`, and `onOpenChange` for menu
|
|
120
|
-
state. Use root `color` for the semantic trigger and menu palette, and
|
|
121
|
-
`Dropdown.Item danger` for destructive commands.
|
|
71
|
+
### Forms
|
|
122
72
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
<Dropdown color='primary' label='Actions'>
|
|
127
|
-
<Dropdown.Content>
|
|
128
|
-
<Dropdown.Label>File</Dropdown.Label>
|
|
129
|
-
<Dropdown.Item value='duplicate' onSelect={duplicate}>
|
|
130
|
-
Duplicate
|
|
131
|
-
</Dropdown.Item>
|
|
132
|
-
<Dropdown.Separator />
|
|
133
|
-
<Dropdown.Item value='delete' danger onSelect={deleteFile}>
|
|
134
|
-
Delete
|
|
135
|
-
</Dropdown.Item>
|
|
136
|
-
</Dropdown.Content>
|
|
137
|
-
</Dropdown>;
|
|
138
|
-
```
|
|
73
|
+
- FormField
|
|
74
|
+
- Tabs
|
|
139
75
|
|
|
140
|
-
|
|
76
|
+
Every public component includes React Native Storybook stories and Vitest unit
|
|
77
|
+
tests.
|
|
141
78
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
or `RadioGroup`. The wrapped control should provide its own
|
|
145
|
-
`accessibilityLabel`, role, disabled/editable state and interaction behavior.
|
|
146
|
-
`FormField` only provides layout, text styling, required mark, disabled root
|
|
147
|
-
state and polite error announcement.
|
|
79
|
+
For detailed props, shared types, examples, and compound component APIs, see
|
|
80
|
+
[Native Component API](./API.md).
|
|
148
81
|
|
|
149
|
-
##
|
|
82
|
+
## Infrastructure
|
|
150
83
|
|
|
151
|
-
|
|
84
|
+
The package also exports native infrastructure used by components and
|
|
85
|
+
applications:
|
|
152
86
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
87
|
+
- `Portal`
|
|
88
|
+
- `PortalProvider`
|
|
89
|
+
- `ThemeProvider`
|
|
90
|
+
- `useTheme`
|
|
91
|
+
- `nativeThemes`
|
|
156
92
|
|
|
157
|
-
|
|
93
|
+
## Documentation
|
|
158
94
|
|
|
159
|
-
|
|
95
|
+
- [Getting Started](https://docs.vellira.dev/start/getting-started)
|
|
96
|
+
- [React Native](https://docs.vellira.dev/react-native/)
|
|
97
|
+
- [Native Component API](./API.md)
|
|
160
98
|
|
|
161
99
|
## Storybook
|
|
162
100
|
|
|
163
|
-
Native stories live next to components as `*.stories.tsx` and are consumed by
|
|
101
|
+
Native stories live next to components as `*.stories.tsx` and are consumed by
|
|
102
|
+
`apps/native-storybook`.
|
|
164
103
|
|
|
165
104
|
Run on-device Storybook from the workspace root:
|
|
166
105
|
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export { Dropdown } from './components/Dropdown';
|
|
|
2
2
|
export type { DropdownContentProps, DropdownEmptyProps, DropdownGroupProps, DropdownItemProps, DropdownLabelProps, DropdownPresentation, DropdownProps, DropdownSelectEvent, DropdownSeparatorProps, DropdownTriggerProps, } from './components/Dropdown/types';
|
|
3
3
|
export type { ModalProps } from './components/Modal';
|
|
4
4
|
export { Modal } from './components/Modal';
|
|
5
|
+
export type { PopoverAnchorProps, PopoverArrowProps, PopoverCloseProps, PopoverContentProps, PopoverDescriptionProps, PopoverProps, PopoverTitleProps, PopoverTriggerProps, } from './components/Popover';
|
|
6
|
+
export { Popover } from './components/Popover';
|
|
5
7
|
export type { RadioGroupProps } from './components/RadioGroup';
|
|
6
8
|
export { RadioGroup } from './components/RadioGroup';
|
|
7
9
|
export type { SelectOption, SelectProps } from './components/Select';
|
package/dist/index.js
CHANGED
|
@@ -3,75 +3,205 @@ import { Check, ChevronDown, Close, Search } from "@vellira-ui/icons";
|
|
|
3
3
|
import { AccessibilityInfo, ActivityIndicator, Animated, Dimensions, Easing, FlatList, Modal as Modal$1, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, View, findNodeHandle, useWindowDimensions } from "react-native";
|
|
4
4
|
import { darkTheme, highContrastTheme, lightTheme } from "@vellira-ui/tokens";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
//#region src/theme/fontWeight.ts
|
|
7
|
+
const nativeFontWeights = /* @__PURE__ */ new Set([
|
|
8
|
+
"normal",
|
|
9
|
+
"bold",
|
|
10
|
+
"100",
|
|
11
|
+
"200",
|
|
12
|
+
"300",
|
|
13
|
+
"400",
|
|
14
|
+
"500",
|
|
15
|
+
"600",
|
|
16
|
+
"700",
|
|
17
|
+
"800",
|
|
18
|
+
"900"
|
|
19
|
+
]);
|
|
20
|
+
function toNativeFontWeight(weight) {
|
|
21
|
+
const normalizedWeight = String(weight);
|
|
22
|
+
if (!nativeFontWeights.has(normalizedWeight)) return "normal";
|
|
23
|
+
return normalizedWeight;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/managers/FloatingManager/computeFloatingPosition.ts
|
|
27
|
+
function clamp(value, min, max) {
|
|
28
|
+
return Math.min(Math.max(value, min), Math.max(min, max));
|
|
29
|
+
}
|
|
30
|
+
function parsePlacement(placement) {
|
|
31
|
+
const [side, align = "center"] = placement.split("-");
|
|
32
|
+
return {
|
|
33
|
+
side,
|
|
34
|
+
align
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function createPlacement(side, align) {
|
|
38
|
+
return align === "center" ? side : `${side}-${align}`;
|
|
39
|
+
}
|
|
40
|
+
function getOppositeSide(side) {
|
|
41
|
+
switch (side) {
|
|
42
|
+
case "top": return "bottom";
|
|
43
|
+
case "right": return "left";
|
|
44
|
+
case "bottom": return "top";
|
|
45
|
+
case "left": return "right";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function computeBasePosition({ reference, floating, side, align, offset }) {
|
|
49
|
+
const referenceCenterX = reference.x + reference.width / 2;
|
|
50
|
+
const referenceCenterY = reference.y + reference.height / 2;
|
|
51
|
+
const alignedLeft = align === "start" ? reference.x : align === "end" ? reference.x + reference.width - floating.width : referenceCenterX - floating.width / 2;
|
|
52
|
+
const alignedTop = align === "start" ? reference.y : align === "end" ? reference.y + reference.height - floating.height : referenceCenterY - floating.height / 2;
|
|
53
|
+
switch (side) {
|
|
54
|
+
case "top": return {
|
|
55
|
+
top: reference.y - floating.height - offset,
|
|
56
|
+
left: alignedLeft
|
|
57
|
+
};
|
|
58
|
+
case "right": return {
|
|
59
|
+
top: alignedTop,
|
|
60
|
+
left: reference.x + reference.width + offset
|
|
61
|
+
};
|
|
62
|
+
case "bottom": return {
|
|
63
|
+
top: reference.y + reference.height + offset,
|
|
64
|
+
left: alignedLeft
|
|
65
|
+
};
|
|
66
|
+
case "left": return {
|
|
67
|
+
top: alignedTop,
|
|
68
|
+
left: reference.x - floating.width - offset
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function getMainAxisOverflow({ side, position, floating, boundary, padding }) {
|
|
73
|
+
switch (side) {
|
|
74
|
+
case "top": return padding - position.top;
|
|
75
|
+
case "right": return position.left + floating.width + padding - boundary.width;
|
|
76
|
+
case "bottom": return position.top + floating.height + padding - boundary.height;
|
|
77
|
+
case "left": return padding - position.left;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function computeFloatingPosition({ reference, floating, boundary, placement, offset = 8, padding = 12, arrowPadding = 16, flip = true, shift = true }) {
|
|
81
|
+
const parsed = parsePlacement(placement);
|
|
82
|
+
let resolvedSide = parsed.side;
|
|
83
|
+
let position = computeBasePosition({
|
|
84
|
+
reference,
|
|
85
|
+
floating,
|
|
86
|
+
side: resolvedSide,
|
|
87
|
+
align: parsed.align,
|
|
88
|
+
offset
|
|
89
|
+
});
|
|
90
|
+
if (flip && getMainAxisOverflow({
|
|
91
|
+
side: resolvedSide,
|
|
92
|
+
position,
|
|
93
|
+
floating,
|
|
94
|
+
boundary,
|
|
95
|
+
padding
|
|
96
|
+
}) > 0) {
|
|
97
|
+
const oppositeSide = getOppositeSide(resolvedSide);
|
|
98
|
+
const oppositePosition = computeBasePosition({
|
|
99
|
+
reference,
|
|
100
|
+
floating,
|
|
101
|
+
side: oppositeSide,
|
|
102
|
+
align: parsed.align,
|
|
103
|
+
offset
|
|
104
|
+
});
|
|
105
|
+
if (getMainAxisOverflow({
|
|
106
|
+
side: oppositeSide,
|
|
107
|
+
position: oppositePosition,
|
|
108
|
+
floating,
|
|
109
|
+
boundary,
|
|
110
|
+
padding
|
|
111
|
+
}) <= 0) {
|
|
112
|
+
resolvedSide = oppositeSide;
|
|
113
|
+
position = oppositePosition;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (shift) position = {
|
|
117
|
+
top: clamp(position.top, padding, boundary.height - floating.height - padding),
|
|
118
|
+
left: clamp(position.left, padding, boundary.width - floating.width - padding)
|
|
119
|
+
};
|
|
120
|
+
const referenceCenterX = reference.x + reference.width / 2;
|
|
121
|
+
const referenceCenterY = reference.y + reference.height / 2;
|
|
122
|
+
const arrowPosition = resolvedSide === "top" || resolvedSide === "bottom" ? { left: clamp(referenceCenterX - position.left, arrowPadding, floating.width - arrowPadding) } : { top: clamp(referenceCenterY - position.top, arrowPadding, floating.height - arrowPadding) };
|
|
123
|
+
return {
|
|
124
|
+
position,
|
|
125
|
+
arrowPosition,
|
|
126
|
+
placement: createPlacement(resolvedSide, parsed.align)
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
6
130
|
//#region src/managers/FloatingManager/useNativeFloatingPosition.ts
|
|
7
|
-
const safePadding = 12;
|
|
8
131
|
function useNativeFloatingPosition(placement = "top", offset = 8) {
|
|
9
|
-
const [
|
|
10
|
-
|
|
11
|
-
|
|
132
|
+
const [result, setResult] = useState({
|
|
133
|
+
position: {
|
|
134
|
+
top: 0,
|
|
135
|
+
left: 0
|
|
136
|
+
},
|
|
137
|
+
arrowPosition: {},
|
|
138
|
+
placement
|
|
12
139
|
});
|
|
13
140
|
const floatingSizeRef = useRef({
|
|
14
141
|
width: 0,
|
|
15
142
|
height: 0
|
|
16
143
|
});
|
|
17
144
|
const lastTriggerRef = useRef(null);
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
}, []);
|
|
21
|
-
const calculatePosition = useCallback((triggerRect, size) => {
|
|
22
|
-
const { width: screenWidth, height: screenHeight } = Dimensions.get("window");
|
|
23
|
-
const [side, align = "center"] = placement.split("-");
|
|
24
|
-
const horizontalTop = side === "bottom" ? triggerRect.y + triggerRect.height + offset : triggerRect.y - size.height - offset;
|
|
25
|
-
const verticalTop = align === "start" ? triggerRect.y : align === "end" ? triggerRect.y + triggerRect.height - size.height : triggerRect.y + triggerRect.height / 2 - size.height / 2;
|
|
26
|
-
const horizontalLeft = align === "start" ? triggerRect.x : align === "end" ? triggerRect.x + triggerRect.width - size.width : triggerRect.x + triggerRect.width / 2 - size.width / 2;
|
|
27
|
-
const verticalLeft = side === "right" ? triggerRect.x + triggerRect.width + offset : triggerRect.x - size.width - offset;
|
|
28
|
-
const rawPosition = side === "left" || side === "right" ? {
|
|
29
|
-
top: verticalTop,
|
|
30
|
-
left: verticalLeft
|
|
31
|
-
} : {
|
|
32
|
-
top: horizontalTop,
|
|
33
|
-
left: horizontalLeft
|
|
34
|
-
};
|
|
35
|
-
return {
|
|
36
|
-
top: clamp(rawPosition.top, safePadding, screenHeight - size.height - safePadding),
|
|
37
|
-
left: clamp(rawPosition.left, safePadding, screenWidth - size.width - safePadding)
|
|
38
|
-
};
|
|
39
|
-
}, [
|
|
40
|
-
placement,
|
|
41
|
-
offset,
|
|
42
|
-
clamp
|
|
43
|
-
]);
|
|
44
|
-
const updatePosition = useCallback((triggerRef, measuredSize = floatingSizeRef.current) => {
|
|
145
|
+
const lastContainerRef = useRef(null);
|
|
146
|
+
const updatePosition = useCallback((triggerRef, containerRef, measuredSize = floatingSizeRef.current) => {
|
|
45
147
|
lastTriggerRef.current = triggerRef;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
148
|
+
lastContainerRef.current = containerRef ?? null;
|
|
149
|
+
const triggerNode = triggerRef.current;
|
|
150
|
+
const containerNode = containerRef?.current;
|
|
151
|
+
if (!triggerNode || typeof triggerNode.measureInWindow !== "function") {
|
|
152
|
+
setResult((current) => ({
|
|
153
|
+
...current,
|
|
154
|
+
position: {
|
|
155
|
+
top: 0,
|
|
156
|
+
left: 0
|
|
157
|
+
}
|
|
158
|
+
}));
|
|
52
159
|
return;
|
|
53
160
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
161
|
+
triggerNode.measureInWindow((x, y, width, height) => {
|
|
162
|
+
const commitPosition = (containerX, containerY, containerWidth, containerHeight) => {
|
|
163
|
+
const nextResult = computeFloatingPosition({
|
|
164
|
+
reference: {
|
|
165
|
+
x: x - containerX,
|
|
166
|
+
y: y - containerY,
|
|
167
|
+
width,
|
|
168
|
+
height
|
|
169
|
+
},
|
|
170
|
+
floating: measuredSize,
|
|
171
|
+
boundary: {
|
|
172
|
+
width: containerWidth,
|
|
173
|
+
height: containerHeight
|
|
174
|
+
},
|
|
175
|
+
placement,
|
|
176
|
+
offset
|
|
177
|
+
});
|
|
178
|
+
setResult(nextResult);
|
|
179
|
+
};
|
|
180
|
+
if (!containerNode || typeof containerNode.measureInWindow !== "function") {
|
|
181
|
+
const window = Dimensions.get("window");
|
|
182
|
+
commitPosition(0, 0, window.width, window.height);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
containerNode.measureInWindow((containerX, containerY, containerWidth, containerHeight) => {
|
|
186
|
+
commitPosition(containerX, containerY, containerWidth, containerHeight);
|
|
187
|
+
});
|
|
61
188
|
});
|
|
62
|
-
}, [
|
|
189
|
+
}, [placement, offset]);
|
|
190
|
+
const onFloatingLayout = useCallback((event) => {
|
|
191
|
+
const { width, height } = event.nativeEvent.layout;
|
|
192
|
+
const nextSize = {
|
|
193
|
+
width,
|
|
194
|
+
height
|
|
195
|
+
};
|
|
196
|
+
floatingSizeRef.current = nextSize;
|
|
197
|
+
if (lastTriggerRef.current) updatePosition(lastTriggerRef.current, lastContainerRef.current ?? void 0, nextSize);
|
|
198
|
+
}, [updatePosition]);
|
|
63
199
|
return {
|
|
64
|
-
position,
|
|
200
|
+
position: result.position,
|
|
201
|
+
arrowPosition: result.arrowPosition,
|
|
202
|
+
placement: result.placement,
|
|
65
203
|
updatePosition,
|
|
66
|
-
onFloatingLayout
|
|
67
|
-
const { width, height } = event.nativeEvent.layout;
|
|
68
|
-
const nextSize = {
|
|
69
|
-
width,
|
|
70
|
-
height
|
|
71
|
-
};
|
|
72
|
-
floatingSizeRef.current = nextSize;
|
|
73
|
-
if (lastTriggerRef.current) updatePosition(lastTriggerRef.current, nextSize);
|
|
74
|
-
}, [updatePosition])
|
|
204
|
+
onFloatingLayout
|
|
75
205
|
};
|
|
76
206
|
}
|
|
77
207
|
//#endregion
|
|
@@ -1772,6 +1902,338 @@ const Modal = Object.assign(ModalRoot, {
|
|
|
1772
1902
|
});
|
|
1773
1903
|
Modal.displayName = "Modal";
|
|
1774
1904
|
//#endregion
|
|
1905
|
+
//#region src/components/Popover/internal/PopoverContext.tsx
|
|
1906
|
+
const PopoverContext = createContext(null);
|
|
1907
|
+
function PopoverProvider({ children, value }) {
|
|
1908
|
+
return /* @__PURE__ */ jsx(PopoverContext.Provider, {
|
|
1909
|
+
value,
|
|
1910
|
+
children
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1913
|
+
function usePopoverContext(componentName) {
|
|
1914
|
+
const context = useContext(PopoverContext);
|
|
1915
|
+
if (!context) throw new Error(`${componentName} must be used within Popover.`);
|
|
1916
|
+
return context;
|
|
1917
|
+
}
|
|
1918
|
+
//#endregion
|
|
1919
|
+
//#region src/components/Popover/Anchor/PopoverAnchor.tsx
|
|
1920
|
+
function PopoverAnchor({ children, asChild = false, ...anchorProps }) {
|
|
1921
|
+
const { anchorRef } = usePopoverContext("Popover.Anchor");
|
|
1922
|
+
if (asChild) return cloneElement(children, { ref: anchorRef });
|
|
1923
|
+
return /* @__PURE__ */ jsx(View, {
|
|
1924
|
+
...anchorProps,
|
|
1925
|
+
ref: anchorRef,
|
|
1926
|
+
children
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
PopoverAnchor.displayName = "Popover.Anchor";
|
|
1930
|
+
//#endregion
|
|
1931
|
+
//#region src/components/Popover/Arrow/PopoverArrow.styles.ts
|
|
1932
|
+
function createPopoverArrowStyles({ theme, side, arrowPosition }) {
|
|
1933
|
+
const tokens = theme.components.popover.arrow;
|
|
1934
|
+
const halfSize = tokens.size / 2;
|
|
1935
|
+
const position = side === "top" || side === "bottom" ? {
|
|
1936
|
+
left: arrowPosition.left ?? 0,
|
|
1937
|
+
transform: [{ translateX: -halfSize }, { rotate: "45deg" }]
|
|
1938
|
+
} : {
|
|
1939
|
+
top: arrowPosition.top ?? 0,
|
|
1940
|
+
transform: [{ translateY: -halfSize }, { rotate: "45deg" }]
|
|
1941
|
+
};
|
|
1942
|
+
const border = { borderColor: tokens.border };
|
|
1943
|
+
switch (side) {
|
|
1944
|
+
case "top":
|
|
1945
|
+
position.bottom = -halfSize;
|
|
1946
|
+
border.borderRightWidth = 1;
|
|
1947
|
+
border.borderBottomWidth = 1;
|
|
1948
|
+
break;
|
|
1949
|
+
case "bottom":
|
|
1950
|
+
position.top = -halfSize;
|
|
1951
|
+
border.borderLeftWidth = 1;
|
|
1952
|
+
border.borderTopWidth = 1;
|
|
1953
|
+
break;
|
|
1954
|
+
case "left":
|
|
1955
|
+
position.right = -halfSize;
|
|
1956
|
+
border.borderRightWidth = 1;
|
|
1957
|
+
border.borderTopWidth = 1;
|
|
1958
|
+
break;
|
|
1959
|
+
case "right":
|
|
1960
|
+
position.left = -halfSize;
|
|
1961
|
+
border.borderLeftWidth = 1;
|
|
1962
|
+
border.borderBottomWidth = 1;
|
|
1963
|
+
break;
|
|
1964
|
+
}
|
|
1965
|
+
return StyleSheet.create({ arrow: {
|
|
1966
|
+
position: "absolute",
|
|
1967
|
+
width: tokens.size,
|
|
1968
|
+
height: tokens.size,
|
|
1969
|
+
backgroundColor: tokens.bg,
|
|
1970
|
+
...position,
|
|
1971
|
+
...border
|
|
1972
|
+
} });
|
|
1973
|
+
}
|
|
1974
|
+
//#endregion
|
|
1975
|
+
//#region src/components/Popover/Arrow/PopoverArrow.tsx
|
|
1976
|
+
function PopoverArrow({ style }) {
|
|
1977
|
+
const { theme } = useTheme();
|
|
1978
|
+
const { placement, arrowPosition } = usePopoverContext("Popover.Arrow");
|
|
1979
|
+
const side = placement.split("-")[0];
|
|
1980
|
+
return /* @__PURE__ */ jsx(View, {
|
|
1981
|
+
accessible: false,
|
|
1982
|
+
pointerEvents: "none",
|
|
1983
|
+
style: [useMemo(() => createPopoverArrowStyles({
|
|
1984
|
+
theme,
|
|
1985
|
+
side,
|
|
1986
|
+
arrowPosition
|
|
1987
|
+
}), [
|
|
1988
|
+
theme,
|
|
1989
|
+
side,
|
|
1990
|
+
arrowPosition
|
|
1991
|
+
]).arrow, style]
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
PopoverArrow.displayName = "Popover.Arrow";
|
|
1995
|
+
//#endregion
|
|
1996
|
+
//#region src/components/Popover/Close/PopoverClose.tsx
|
|
1997
|
+
const PopoverClose = ({ children, asChild = false, onPress, ...closeProps }) => {
|
|
1998
|
+
const { setOpen } = usePopoverContext("Popover.Close");
|
|
1999
|
+
const handlePress = (event) => {
|
|
2000
|
+
children.props.onPress?.(event);
|
|
2001
|
+
onPress?.(event);
|
|
2002
|
+
if (event.defaultPrevented) return;
|
|
2003
|
+
setOpen(false, { reason: "close" });
|
|
2004
|
+
};
|
|
2005
|
+
if (asChild) return cloneElement(children, { onPress: handlePress });
|
|
2006
|
+
return /* @__PURE__ */ jsx(Pressable, {
|
|
2007
|
+
...closeProps,
|
|
2008
|
+
onPress: handlePress,
|
|
2009
|
+
children
|
|
2010
|
+
});
|
|
2011
|
+
};
|
|
2012
|
+
PopoverClose.displayName = "PopoverClose";
|
|
2013
|
+
//#endregion
|
|
2014
|
+
//#region src/primitives/Portal/Portal.tsx
|
|
2015
|
+
const PortalContext = createContext(null);
|
|
2016
|
+
const styles$1 = StyleSheet.create({ host: { flex: 1 } });
|
|
2017
|
+
const PortalProvider = ({ children, container = null }) => /* @__PURE__ */ jsx(PortalContext.Provider, {
|
|
2018
|
+
value: container,
|
|
2019
|
+
children
|
|
2020
|
+
});
|
|
2021
|
+
const Portal = ({ children, visible = true, animationType = "none", hardwareAccelerated = true, navigationBarTranslucent = true, statusBarTranslucent = true, onDismiss, onRequestClose }) => {
|
|
2022
|
+
useContext(PortalContext);
|
|
2023
|
+
if (!visible) return null;
|
|
2024
|
+
return /* @__PURE__ */ jsx(Modal$1, {
|
|
2025
|
+
animationType,
|
|
2026
|
+
hardwareAccelerated,
|
|
2027
|
+
navigationBarTranslucent,
|
|
2028
|
+
onDismiss,
|
|
2029
|
+
onRequestClose,
|
|
2030
|
+
presentationStyle: "overFullScreen",
|
|
2031
|
+
statusBarTranslucent,
|
|
2032
|
+
transparent: true,
|
|
2033
|
+
visible: true,
|
|
2034
|
+
children: /* @__PURE__ */ jsx(View, {
|
|
2035
|
+
pointerEvents: "box-none",
|
|
2036
|
+
style: styles$1.host,
|
|
2037
|
+
children
|
|
2038
|
+
})
|
|
2039
|
+
});
|
|
2040
|
+
};
|
|
2041
|
+
Portal.__velliraPortal = true;
|
|
2042
|
+
Portal.displayName = "Portal";
|
|
2043
|
+
PortalProvider.displayName = "PortalProvider";
|
|
2044
|
+
//#endregion
|
|
2045
|
+
//#region src/components/Popover/Content/PopoverContent.styles.ts
|
|
2046
|
+
const styles = StyleSheet.create({
|
|
2047
|
+
layer: { flex: 1 },
|
|
2048
|
+
backdrop: StyleSheet.absoluteFill,
|
|
2049
|
+
content: { position: "absolute" }
|
|
2050
|
+
});
|
|
2051
|
+
function createPopoverContentStyles(theme) {
|
|
2052
|
+
const tokens = theme.components.popover.content;
|
|
2053
|
+
const shadow = tokens.shadow.native;
|
|
2054
|
+
return StyleSheet.create({ content: {
|
|
2055
|
+
minWidth: tokens.minWidth,
|
|
2056
|
+
maxWidth: tokens.maxWidth,
|
|
2057
|
+
padding: tokens.padding,
|
|
2058
|
+
gap: tokens.gap,
|
|
2059
|
+
backgroundColor: tokens.bg,
|
|
2060
|
+
borderColor: tokens.border,
|
|
2061
|
+
borderWidth: tokens.borderWidth,
|
|
2062
|
+
borderRadius: tokens.radius,
|
|
2063
|
+
shadowColor: shadow.color,
|
|
2064
|
+
shadowOpacity: shadow.opacity,
|
|
2065
|
+
shadowRadius: shadow.blur,
|
|
2066
|
+
shadowOffset: {
|
|
2067
|
+
width: shadow.x,
|
|
2068
|
+
height: shadow.y
|
|
2069
|
+
},
|
|
2070
|
+
elevation: shadow.elevation
|
|
2071
|
+
} });
|
|
2072
|
+
}
|
|
2073
|
+
//#endregion
|
|
2074
|
+
//#region src/components/Popover/Content/PopoverContent.tsx
|
|
2075
|
+
function PopoverContent({ children, style, ...contentProps }) {
|
|
2076
|
+
const { theme } = useTheme();
|
|
2077
|
+
const layerRef = useRef(null);
|
|
2078
|
+
const themedStyles = useMemo(() => createPopoverContentStyles(theme), [theme]);
|
|
2079
|
+
const { open, position, onFloatingLayout, updatePosition, setOpen, closeOnOutsidePress } = usePopoverContext("Popover.Content");
|
|
2080
|
+
useEffect(() => {
|
|
2081
|
+
if (!open) return;
|
|
2082
|
+
requestAnimationFrame(() => {
|
|
2083
|
+
updatePosition(layerRef);
|
|
2084
|
+
});
|
|
2085
|
+
}, [open, updatePosition]);
|
|
2086
|
+
return /* @__PURE__ */ jsx(Portal, {
|
|
2087
|
+
visible: open,
|
|
2088
|
+
onRequestClose: () => {
|
|
2089
|
+
setOpen(false, { reason: "escape-key" });
|
|
2090
|
+
},
|
|
2091
|
+
children: /* @__PURE__ */ jsxs(View, {
|
|
2092
|
+
ref: layerRef,
|
|
2093
|
+
pointerEvents: "box-none",
|
|
2094
|
+
style: styles.layer,
|
|
2095
|
+
children: [/* @__PURE__ */ jsx(Pressable, {
|
|
2096
|
+
testID: "popover-backdrop",
|
|
2097
|
+
accessibilityLabel: closeOnOutsidePress ? "Close popover" : void 0,
|
|
2098
|
+
accessibilityRole: closeOnOutsidePress ? "button" : void 0,
|
|
2099
|
+
onPress: closeOnOutsidePress ? () => {
|
|
2100
|
+
setOpen(false, { reason: "outside-press" });
|
|
2101
|
+
} : void 0,
|
|
2102
|
+
style: styles.backdrop
|
|
2103
|
+
}), /* @__PURE__ */ jsx(View, {
|
|
2104
|
+
...contentProps,
|
|
2105
|
+
onLayout: onFloatingLayout,
|
|
2106
|
+
style: [
|
|
2107
|
+
styles.content,
|
|
2108
|
+
themedStyles.content,
|
|
2109
|
+
position,
|
|
2110
|
+
style
|
|
2111
|
+
],
|
|
2112
|
+
children
|
|
2113
|
+
})]
|
|
2114
|
+
})
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
//#endregion
|
|
2118
|
+
//#region src/components/Popover/Description/PopoverDescription.styles.ts
|
|
2119
|
+
function createPopoverDescriptionStyles(theme) {
|
|
2120
|
+
return StyleSheet.create({ description: {
|
|
2121
|
+
color: theme.components.popover.description.fg,
|
|
2122
|
+
fontSize: theme.tokens.typography.size.sm
|
|
2123
|
+
} });
|
|
2124
|
+
}
|
|
2125
|
+
//#endregion
|
|
2126
|
+
//#region src/components/Popover/Description/PopoverDescription.tsx
|
|
2127
|
+
const PopoverDescription = ({ children, style, ...descriptionProps }) => {
|
|
2128
|
+
const { theme } = useTheme();
|
|
2129
|
+
const styles = useMemo(() => createPopoverDescriptionStyles(theme), [theme]);
|
|
2130
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
2131
|
+
...descriptionProps,
|
|
2132
|
+
style: [styles.description, style],
|
|
2133
|
+
children
|
|
2134
|
+
});
|
|
2135
|
+
};
|
|
2136
|
+
PopoverDescription.displayName = "PopoverDescription";
|
|
2137
|
+
//#endregion
|
|
2138
|
+
//#region src/components/Popover/Root/PopoverRoot.tsx
|
|
2139
|
+
function getNativePlacement(side, align) {
|
|
2140
|
+
if (!side) return "bottom";
|
|
2141
|
+
if (!align || align === "center") return side;
|
|
2142
|
+
return `${side}-${align}`;
|
|
2143
|
+
}
|
|
2144
|
+
function PopoverRoot({ children, open: openProp, defaultOpen = false, onOpenChange, side = "bottom", align = "center", sideOffset = 8, closeOnOutsidePress = true }) {
|
|
2145
|
+
const triggerRef = useRef(null);
|
|
2146
|
+
const anchorRef = useRef(null);
|
|
2147
|
+
const getReferenceRef = useCallback(() => anchorRef.current ? anchorRef : triggerRef, []);
|
|
2148
|
+
const openChangeDetailsRef = useRef({ reason: "programmatic" });
|
|
2149
|
+
const [open, setOpenState] = useControllableState({
|
|
2150
|
+
value: openProp,
|
|
2151
|
+
defaultValue: defaultOpen,
|
|
2152
|
+
onChange: (nextOpen) => {
|
|
2153
|
+
onOpenChange?.(nextOpen, openChangeDetailsRef.current);
|
|
2154
|
+
}
|
|
2155
|
+
});
|
|
2156
|
+
const { position, arrowPosition, placement, updatePosition: updateFloatingPosition, onFloatingLayout } = useNativeFloatingPosition(getNativePlacement(side, align), sideOffset);
|
|
2157
|
+
const setOpen = useCallback((nextOpen, details) => {
|
|
2158
|
+
openChangeDetailsRef.current = details;
|
|
2159
|
+
setOpenState(nextOpen);
|
|
2160
|
+
}, [setOpenState]);
|
|
2161
|
+
return /* @__PURE__ */ jsx(PopoverProvider, {
|
|
2162
|
+
value: {
|
|
2163
|
+
open,
|
|
2164
|
+
closeOnOutsidePress,
|
|
2165
|
+
triggerRef,
|
|
2166
|
+
anchorRef,
|
|
2167
|
+
side,
|
|
2168
|
+
align,
|
|
2169
|
+
placement,
|
|
2170
|
+
position,
|
|
2171
|
+
arrowPosition,
|
|
2172
|
+
onFloatingLayout,
|
|
2173
|
+
updatePosition: useCallback((containerRef) => {
|
|
2174
|
+
updateFloatingPosition(getReferenceRef(), containerRef);
|
|
2175
|
+
}, [getReferenceRef, updateFloatingPosition]),
|
|
2176
|
+
setOpen
|
|
2177
|
+
},
|
|
2178
|
+
children
|
|
2179
|
+
});
|
|
2180
|
+
}
|
|
2181
|
+
PopoverRoot.displayName = "Popover.Root";
|
|
2182
|
+
//#endregion
|
|
2183
|
+
//#region src/components/Popover/Title/PopoverTitle.styles.ts
|
|
2184
|
+
function createPopoverTitleStyles(theme) {
|
|
2185
|
+
return StyleSheet.create({ title: {
|
|
2186
|
+
color: theme.components.popover.title.fg,
|
|
2187
|
+
fontSize: theme.tokens.typography.size.md,
|
|
2188
|
+
fontWeight: toNativeFontWeight(theme.tokens.typography.weight.semibold)
|
|
2189
|
+
} });
|
|
2190
|
+
}
|
|
2191
|
+
//#endregion
|
|
2192
|
+
//#region src/components/Popover/Title/PopoverTitle.tsx
|
|
2193
|
+
const PopoverTitle = ({ children, style, ...titleProps }) => {
|
|
2194
|
+
const { theme } = useTheme();
|
|
2195
|
+
const styles = useMemo(() => createPopoverTitleStyles(theme), [theme]);
|
|
2196
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
2197
|
+
...titleProps,
|
|
2198
|
+
style: [styles.title, style],
|
|
2199
|
+
children
|
|
2200
|
+
});
|
|
2201
|
+
};
|
|
2202
|
+
PopoverTitle.displayName = "PopoverTitle";
|
|
2203
|
+
//#endregion
|
|
2204
|
+
//#region src/components/Popover/Trigger/PopoverTrigger.tsx
|
|
2205
|
+
function PopoverTrigger({ children, asChild = false, onPress, ...triggerProps }) {
|
|
2206
|
+
const { open, setOpen, triggerRef } = usePopoverContext("Popover.Trigger");
|
|
2207
|
+
const handlePress = (event) => {
|
|
2208
|
+
children.props.onPress?.(event);
|
|
2209
|
+
onPress?.(event);
|
|
2210
|
+
if (event.defaultPrevented) return;
|
|
2211
|
+
setOpen(!open, { reason: "trigger" });
|
|
2212
|
+
};
|
|
2213
|
+
if (asChild) return cloneElement(children, {
|
|
2214
|
+
ref: triggerRef,
|
|
2215
|
+
onPress: handlePress
|
|
2216
|
+
});
|
|
2217
|
+
return /* @__PURE__ */ jsx(Pressable, {
|
|
2218
|
+
...triggerProps,
|
|
2219
|
+
ref: triggerRef,
|
|
2220
|
+
onPress: handlePress,
|
|
2221
|
+
children
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
2224
|
+
//#endregion
|
|
2225
|
+
//#region src/components/Popover/Popover.tsx
|
|
2226
|
+
const Popover = Object.assign(PopoverRoot, {
|
|
2227
|
+
Anchor: PopoverAnchor,
|
|
2228
|
+
Trigger: PopoverTrigger,
|
|
2229
|
+
Content: PopoverContent,
|
|
2230
|
+
Arrow: PopoverArrow,
|
|
2231
|
+
Close: PopoverClose,
|
|
2232
|
+
Title: PopoverTitle,
|
|
2233
|
+
Description: PopoverDescription
|
|
2234
|
+
});
|
|
2235
|
+
Popover.displayName = "Popover";
|
|
2236
|
+
//#endregion
|
|
1775
2237
|
//#region src/components/RadioGroup/internal/RadioGroupContext.tsx
|
|
1776
2238
|
const RadioGroupContext = createContext(null);
|
|
1777
2239
|
const RadioGroupProvider = RadioGroupContext.Provider;
|
|
@@ -5673,18 +6135,4 @@ const Input = forwardRef(({ label, description, value, defaultValue, onValueChan
|
|
|
5673
6135
|
});
|
|
5674
6136
|
Input.displayName = "Input";
|
|
5675
6137
|
//#endregion
|
|
5676
|
-
|
|
5677
|
-
const PortalContext = createContext(null);
|
|
5678
|
-
const PortalProvider = ({ children, container = null }) => /* @__PURE__ */ jsx(PortalContext.Provider, {
|
|
5679
|
-
value: container,
|
|
5680
|
-
children
|
|
5681
|
-
});
|
|
5682
|
-
const Portal = ({ children }) => {
|
|
5683
|
-
useContext(PortalContext);
|
|
5684
|
-
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
5685
|
-
};
|
|
5686
|
-
Portal.__velliraPortal = true;
|
|
5687
|
-
Portal.displayName = "Portal";
|
|
5688
|
-
PortalProvider.displayName = "PortalProvider";
|
|
5689
|
-
//#endregion
|
|
5690
|
-
export { Button, Checkbox, Dropdown, FormField, Input, Modal, Portal, PortalProvider, Radio, RadioGroup, Select, Tabs, ThemeProvider, Tooltip, nativeThemes, useTheme };
|
|
6138
|
+
export { Button, Checkbox, Dropdown, FormField, Input, Modal, Popover, Portal, PortalProvider, Radio, RadioGroup, Select, Tabs, ThemeProvider, Tooltip, nativeThemes, useTheme };
|