expo-snowui 1.0.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/LICENSE.txt +674 -0
- package/README.md +16 -0
- package/lib/module/component/snow-break.js +15 -0
- package/lib/module/component/snow-break.js.map +1 -0
- package/lib/module/component/snow-dropdown.js +66 -0
- package/lib/module/component/snow-dropdown.js.map +1 -0
- package/lib/module/component/snow-fill-view.js +40 -0
- package/lib/module/component/snow-fill-view.js.map +1 -0
- package/lib/module/component/snow-grid.js +70 -0
- package/lib/module/component/snow-grid.js.map +1 -0
- package/lib/module/component/snow-header.js +20 -0
- package/lib/module/component/snow-header.js.map +1 -0
- package/lib/module/component/snow-image-button.js +98 -0
- package/lib/module/component/snow-image-button.js.map +1 -0
- package/lib/module/component/snow-image-grid.js +84 -0
- package/lib/module/component/snow-image-grid.js.map +1 -0
- package/lib/module/component/snow-input.js +51 -0
- package/lib/module/component/snow-input.js.map +1 -0
- package/lib/module/component/snow-label.js +20 -0
- package/lib/module/component/snow-label.js.map +1 -0
- package/lib/module/component/snow-modal.js +49 -0
- package/lib/module/component/snow-modal.js.map +1 -0
- package/lib/module/component/snow-range-slider.js +247 -0
- package/lib/module/component/snow-range-slider.js.map +1 -0
- package/lib/module/component/snow-safe-area.js +22 -0
- package/lib/module/component/snow-safe-area.js.map +1 -0
- package/lib/module/component/snow-tabs.js +62 -0
- package/lib/module/component/snow-tabs.js.map +1 -0
- package/lib/module/component/snow-text-button.js +102 -0
- package/lib/module/component/snow-text-button.js.map +1 -0
- package/lib/module/component/snow-text.js +34 -0
- package/lib/module/component/snow-text.js.map +1 -0
- package/lib/module/component/snow-toggle.js +38 -0
- package/lib/module/component/snow-toggle.js.map +1 -0
- package/lib/module/context/snow-focus-context.js +39 -0
- package/lib/module/context/snow-focus-context.js.map +1 -0
- package/lib/module/context/snow-style-context.js +54 -0
- package/lib/module/context/snow-style-context.js.map +1 -0
- package/lib/module/index.js +63 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/snow-app.js +40 -0
- package/lib/module/snow-app.js.map +1 -0
- package/lib/module/snow-style.js +392 -0
- package/lib/module/snow-style.js.map +1 -0
- package/lib/module/snow-ui.js +19 -0
- package/lib/module/snow-ui.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/index.d.ts +42 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +163 -0
- package/src/component/snow-break.js +9 -0
- package/src/component/snow-dropdown.js +64 -0
- package/src/component/snow-fill-view.js +36 -0
- package/src/component/snow-grid.js +74 -0
- package/src/component/snow-header.js +18 -0
- package/src/component/snow-image-button.js +98 -0
- package/src/component/snow-image-grid.js +87 -0
- package/src/component/snow-input.js +47 -0
- package/src/component/snow-label.js +18 -0
- package/src/component/snow-modal.js +46 -0
- package/src/component/snow-range-slider.js +263 -0
- package/src/component/snow-safe-area.js +17 -0
- package/src/component/snow-tabs.js +62 -0
- package/src/component/snow-text-button.js +106 -0
- package/src/component/snow-text.js +31 -0
- package/src/component/snow-toggle.js +32 -0
- package/src/context/snow-focus-context.js +37 -0
- package/src/context/snow-style-context.js +46 -0
- package/src/index.tsx +63 -0
- package/src/snow-app.js +27 -0
- package/src/snow-style.js +399 -0
- package/src/snow-ui.js +16 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
PanResponder,
|
|
4
|
+
Pressable,
|
|
5
|
+
View,
|
|
6
|
+
findNodeHandle
|
|
7
|
+
} from "react-native";
|
|
8
|
+
import { useDebouncedCallback } from 'use-debounce'
|
|
9
|
+
import { useFocusContext } from '../context/snow-focus-context'
|
|
10
|
+
import { useStyleContext } from '../context/snow-style-context'
|
|
11
|
+
|
|
12
|
+
const min = 0.0
|
|
13
|
+
const max = 1.0
|
|
14
|
+
const step = 0.01
|
|
15
|
+
|
|
16
|
+
/* spread props
|
|
17
|
+
nextFocusLeft
|
|
18
|
+
nextFocusRight
|
|
19
|
+
nextFocusUp
|
|
20
|
+
nextFocusDown
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// This is a tricky component because props.value can be debounced
|
|
24
|
+
// The numerical value the component provides is `percent`
|
|
25
|
+
// This is a float between 0.0 and 1.0, to ease multiplication
|
|
26
|
+
|
|
27
|
+
// The slider has two primary components, the thumb and the track.
|
|
28
|
+
// The thumb's position within the track determines the `percent` used throughout the component
|
|
29
|
+
// However, that position can also be decided by the parent by passing in a percent
|
|
30
|
+
// When the component is being manually updated, then parent updates should be ignored
|
|
31
|
+
// Otherwise the parent should take priority
|
|
32
|
+
|
|
33
|
+
// I first tried @react-native-community/slider
|
|
34
|
+
// It did not offer enough customization of the track and thumb
|
|
35
|
+
|
|
36
|
+
// I then tried @react-native-assets/slider
|
|
37
|
+
// It completely breaks in Android or Web, depending on the version of react I override it to use
|
|
38
|
+
|
|
39
|
+
// After a handful of other libraries still had problems, I rolled my own
|
|
40
|
+
export function SnowRangeSlider(props) {
|
|
41
|
+
const { SnowStyle, SnowConfig } = useStyleContext(props)
|
|
42
|
+
const { allowFocusing, setLockedElement, lockedElement } = useFocusContext()
|
|
43
|
+
|
|
44
|
+
const isDraggingRef = React.useRef(false)
|
|
45
|
+
const [percent, setPercent] = React.useState(0)
|
|
46
|
+
const percentRef = React.useRef(percent)
|
|
47
|
+
const [thumbFocus, setThumbFocus] = React.useState(false)
|
|
48
|
+
const elementRef = React.useRef(null)
|
|
49
|
+
const [applyStepInterval, setApplyStepInterval] = React.useState(null)
|
|
50
|
+
|
|
51
|
+
let sliderWidth = SnowStyle.component.rangeSlider.trackWrapper.width
|
|
52
|
+
if (props.width) {
|
|
53
|
+
sliderWidth = props.width
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const layoutsRef = React.useRef({
|
|
57
|
+
slider: sliderWidth,
|
|
58
|
+
track: 0,
|
|
59
|
+
thumb: 0,
|
|
60
|
+
leftTrack: 0,
|
|
61
|
+
rightTrack: sliderWidth
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
let onValueChange = props.onValueChange
|
|
65
|
+
if (props.debounce) {
|
|
66
|
+
onValueChange = useDebouncedCallback(props.onValueChange, SnowConfig.inputDebounceMilliseconds)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const thumbPositionToPercent = (positionX) => {
|
|
70
|
+
let actionPositionX = positionX - layoutsRef.current.track.x - (layoutsRef.current.thumb.width / 2)
|
|
71
|
+
if (actionPositionX < 0) {
|
|
72
|
+
actionPositionX = 0
|
|
73
|
+
}
|
|
74
|
+
if (actionPositionX > sliderWidth) {
|
|
75
|
+
actionPositionX = sliderWidth
|
|
76
|
+
}
|
|
77
|
+
let newPercent = actionPositionX / sliderWidth
|
|
78
|
+
if (newPercent < 0) {
|
|
79
|
+
newPercent = 0
|
|
80
|
+
}
|
|
81
|
+
if (newPercent > 1) {
|
|
82
|
+
newPercent = 1
|
|
83
|
+
}
|
|
84
|
+
setPercent(newPercent)
|
|
85
|
+
percentRef.current = newPercent
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const panRef = React.useRef(
|
|
89
|
+
PanResponder.create({
|
|
90
|
+
onStartShouldSetPanResponder: () => true,
|
|
91
|
+
onMoveShouldSetPanResponder: () => true,
|
|
92
|
+
onPanResponderEnd: () => {
|
|
93
|
+
isDraggingRef.current = false
|
|
94
|
+
onValueChange(percentRef.current)
|
|
95
|
+
},
|
|
96
|
+
onPanResponderRelease: () => {
|
|
97
|
+
isDraggingRef.current = false
|
|
98
|
+
onValueChange(percentRef.current)
|
|
99
|
+
},
|
|
100
|
+
onPanResponderMove: (pressEvent, gestureState) => {
|
|
101
|
+
isDraggingRef.current = true
|
|
102
|
+
let positionX = gestureState.moveX
|
|
103
|
+
if (!positionX) {
|
|
104
|
+
positionX = gestureState.x0
|
|
105
|
+
}
|
|
106
|
+
thumbPositionToPercent(positionX)
|
|
107
|
+
},
|
|
108
|
+
onPanResponderGrant: (pressEvent, gestureState) => {
|
|
109
|
+
isDraggingRef.current = true
|
|
110
|
+
let positionX = gestureState.moveX
|
|
111
|
+
if (!positionX) {
|
|
112
|
+
positionX = gestureState.x0
|
|
113
|
+
}
|
|
114
|
+
thumbPositionToPercent(positionX)
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
React.useEffect(() => {
|
|
120
|
+
if (!isDraggingRef.current) {
|
|
121
|
+
setPercent(props.percent)
|
|
122
|
+
}
|
|
123
|
+
}, [props.percent])
|
|
124
|
+
|
|
125
|
+
React.useEffect(() => {
|
|
126
|
+
percentRef.current = percent
|
|
127
|
+
}, [percent])
|
|
128
|
+
|
|
129
|
+
React.useEffect(() => {
|
|
130
|
+
if (props.setRemoteCallbacks) {
|
|
131
|
+
props.setRemoteCallbacks((callbacks) => {
|
|
132
|
+
callbacks['slider'] = sliderHandleRemote
|
|
133
|
+
return callbacks
|
|
134
|
+
})
|
|
135
|
+
return () => {
|
|
136
|
+
props.setRemoteCallbacks((callbacks) => {
|
|
137
|
+
callbacks['slider'] = null
|
|
138
|
+
return callbacks
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const applyStep = (amount) => {
|
|
146
|
+
let result = percentRef.current + amount
|
|
147
|
+
if (result < min) {
|
|
148
|
+
result = min
|
|
149
|
+
}
|
|
150
|
+
if (result > max) {
|
|
151
|
+
result = max
|
|
152
|
+
}
|
|
153
|
+
percentRef.current = result
|
|
154
|
+
setPercent(result)
|
|
155
|
+
onValueChange(result)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const longPress = (amount, action) => {
|
|
159
|
+
if (action === 0) {
|
|
160
|
+
applyStep(amount)
|
|
161
|
+
setApplyStepInterval(setInterval(() => { applyStep(amount) }, 100))
|
|
162
|
+
}
|
|
163
|
+
if (action === 1) {
|
|
164
|
+
clearInterval(applyStepInterval)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const sliderHandleRemote = (kind, action) => {
|
|
169
|
+
if (lockedElement) {
|
|
170
|
+
if (kind === 'right') {
|
|
171
|
+
applyStep(step)
|
|
172
|
+
clearInterval(applyStepInterval)
|
|
173
|
+
}
|
|
174
|
+
else if (kind === 'longRight') {
|
|
175
|
+
longPress(step * 2, action)
|
|
176
|
+
}
|
|
177
|
+
else if (kind === 'left') {
|
|
178
|
+
applyStep(-step)
|
|
179
|
+
clearInterval(applyStepInterval)
|
|
180
|
+
}
|
|
181
|
+
else if (kind === 'longLeft') {
|
|
182
|
+
longPress(-step * 2, action)
|
|
183
|
+
}
|
|
184
|
+
else if (kind === 'down') {
|
|
185
|
+
focusThumb(false)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const focusThumb = (focus) => {
|
|
191
|
+
if (allowFocusing) {
|
|
192
|
+
if (focus !== thumbFocus) {
|
|
193
|
+
if (focus) {
|
|
194
|
+
setLockedElement(findNodeHandle(elementRef.current))
|
|
195
|
+
} else {
|
|
196
|
+
setLockedElement(null)
|
|
197
|
+
}
|
|
198
|
+
setThumbFocus(focus)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const trackWrapperStyle = [
|
|
204
|
+
SnowStyle.component.rangeSlider.trackWrapper,
|
|
205
|
+
{
|
|
206
|
+
width: sliderWidth
|
|
207
|
+
}
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
let thumbX = 0
|
|
211
|
+
if (isDraggingRef.current) {
|
|
212
|
+
thumbX = percent * sliderWidth
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
thumbX = percentRef.current * sliderWidth
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const leftTrackStyle = [
|
|
219
|
+
SnowStyle.component.rangeSlider.leftTrack,
|
|
220
|
+
{
|
|
221
|
+
width: thumbX
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
let thumbStyle = [
|
|
226
|
+
SnowStyle.component.rangeSlider.thumb,
|
|
227
|
+
{
|
|
228
|
+
left: thumbX - SnowStyle.component.rangeSlider.thumb.width / 2
|
|
229
|
+
}
|
|
230
|
+
]
|
|
231
|
+
if (thumbFocus) {
|
|
232
|
+
thumbStyle.push({
|
|
233
|
+
backgroundColor: 'white'
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const handleLayout = (kind) => {
|
|
238
|
+
return (event) => {
|
|
239
|
+
let widths = { ...layoutsRef.current }
|
|
240
|
+
widths[kind] = event.nativeEvent.layout
|
|
241
|
+
layoutsRef.current = widths
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<View onLayout={handleLayout('slider')} style={SnowStyle.component.rangeSlider.wrapper}>
|
|
247
|
+
<View {...panRef.current.panHandlers} onLayout={handleLayout('track')} style={trackWrapperStyle}>
|
|
248
|
+
<View onLayout={handleLayout('leftTrack')} style={leftTrackStyle} />
|
|
249
|
+
<View onLayout={handleLayout('rightTrack')} style={SnowStyle.component.rangeSlider.rightTrack} />
|
|
250
|
+
<Pressable
|
|
251
|
+
{...props}
|
|
252
|
+
onLayout={handleLayout('thumb')}
|
|
253
|
+
ref={elementRef}
|
|
254
|
+
onPress={() => { focusThumb(true) }}
|
|
255
|
+
onFocus={() => { focusThumb(true) }}
|
|
256
|
+
focusable={true}
|
|
257
|
+
style={thumbStyle} />
|
|
258
|
+
</View>
|
|
259
|
+
</View>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export default SnowRangeSlider
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { View, TVFocusGuideView } from 'react-native'
|
|
2
|
+
import { useStyleContext } from '../context/snow-style-context'
|
|
3
|
+
|
|
4
|
+
export function SnowSafeArea(props) {
|
|
5
|
+
const { SnowStyle } = useStyleContext(props)
|
|
6
|
+
let Wrapper = View
|
|
7
|
+
if (SnowStyle.isTV) {
|
|
8
|
+
Wrapper = TVFocusGuideView
|
|
9
|
+
}
|
|
10
|
+
return (
|
|
11
|
+
<Wrapper autoFocus snowStyle={SnowStyle} style={SnowStyle.component.safeArea}>
|
|
12
|
+
{props.children}
|
|
13
|
+
</Wrapper>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default SnowSafeArea
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import SnowDropdown from './snow-dropdown'
|
|
5
|
+
import { useStyleContext } from '../context/snow-style-context'
|
|
6
|
+
|
|
7
|
+
export function SnowTabs(props) {
|
|
8
|
+
const { SnowStyle } = useStyleContext(props)
|
|
9
|
+
if (!props.headers) {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
if (!props.children) {
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const [tabIndex, setTabIndex] = React.useState(0)
|
|
17
|
+
const tabStyle = {
|
|
18
|
+
color: {
|
|
19
|
+
fade: SnowStyle.color.panel
|
|
20
|
+
},
|
|
21
|
+
component: {
|
|
22
|
+
textButton: {
|
|
23
|
+
fade: {
|
|
24
|
+
backgroundColor: SnowStyle.color.panel
|
|
25
|
+
},
|
|
26
|
+
fadeText: {
|
|
27
|
+
color: SnowStyle.color.text
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
imageButton: {
|
|
31
|
+
wrapper: {
|
|
32
|
+
borderColor: SnowStyle.color.panel
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const tabs = React.Children.toArray(props.children).map(child => {
|
|
39
|
+
if (React.isValidElement(child)) {
|
|
40
|
+
return React.cloneElement(child, { snowStyle: tabStyle });
|
|
41
|
+
}
|
|
42
|
+
return child;
|
|
43
|
+
}).filter(child => child !== null);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<View>
|
|
47
|
+
<SnowDropdown
|
|
48
|
+
snowStyle={tabStyle}
|
|
49
|
+
fade
|
|
50
|
+
options={props.headers}
|
|
51
|
+
onValueChange={setTabIndex}
|
|
52
|
+
valueIndex={tabIndex}
|
|
53
|
+
itemsPerRow={props.headers.length} />
|
|
54
|
+
<View style={SnowStyle.component.tabs.panel}>
|
|
55
|
+
{tabs[tabIndex]}
|
|
56
|
+
</View>
|
|
57
|
+
</View>
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default SnowTabs
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Pressable, Keyboard } from 'react-native';
|
|
3
|
+
import { useFocusContext } from '../context/snow-focus-context'
|
|
4
|
+
import { useStyleContext } from '../context/snow-style-context'
|
|
5
|
+
import SnowText from './snow-text'
|
|
6
|
+
|
|
7
|
+
/* spread props
|
|
8
|
+
nextFocusLeft
|
|
9
|
+
nextFocusRight
|
|
10
|
+
nextFocusUp
|
|
11
|
+
nextFocusDown
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
export function SnowTextButton(props) {
|
|
16
|
+
const { SnowStyle } = useStyleContext(props)
|
|
17
|
+
const { focusIsLocked } = useFocusContext()
|
|
18
|
+
const [focused, setFocused] = React.useState(false)
|
|
19
|
+
const touchRef = React.useRef(null)
|
|
20
|
+
|
|
21
|
+
React.useEffect(() => {
|
|
22
|
+
if (props.shouldFocus && !Keyboard.isVisible()) {
|
|
23
|
+
touchRef.current.focus()
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const wrapperStyle = [SnowStyle.component.textButton.wrapper]
|
|
28
|
+
if (props.disabled) {
|
|
29
|
+
wrapperStyle.push(SnowStyle.component.textButton.disabled)
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
if (props.selected) {
|
|
33
|
+
wrapperStyle.push(SnowStyle.component.textButton.selected)
|
|
34
|
+
}
|
|
35
|
+
if (focused) {
|
|
36
|
+
wrapperStyle.push(SnowStyle.component.textButton.focused)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (props.fade) {
|
|
41
|
+
wrapperStyle.push(SnowStyle.component.textButton.fade)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (props.tall && SnowStyle.isWeb) {
|
|
45
|
+
wrapperStyle.push(SnowStyle.component.textButton.tallWrapper)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (props.style) {
|
|
49
|
+
wrapperStyle.push(props.style)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let textStyle = [SnowStyle.component.textButton.text]
|
|
53
|
+
if (props.title.length > 18) {
|
|
54
|
+
textStyle.push(SnowStyle.component.textButton.smallText)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (props.fade) {
|
|
58
|
+
textStyle.push(SnowStyle.component.textButton.fadeText)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (props.short) {
|
|
62
|
+
wrapperStyle.push(SnowStyle.component.textButton.shortWrapper)
|
|
63
|
+
textStyle.push(SnowStyle.component.textButton.smallText)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const onPressUnlessTyping = () => {
|
|
67
|
+
if (props.onPress && !Keyboard.isVisible()) {
|
|
68
|
+
return props.onPress()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const onLongPressUnlessTyping = () => {
|
|
73
|
+
if (props.onLongPress && !Keyboard.isVisible()) {
|
|
74
|
+
return props.onLongPress()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let allowFocus = !focusIsLocked && !Keyboard.isVisible()
|
|
79
|
+
|
|
80
|
+
const changeFocus = (focus) => {
|
|
81
|
+
if (!focusIsLocked) {
|
|
82
|
+
setFocused(focus)
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
setFocused(false)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<Pressable
|
|
91
|
+
{...props}
|
|
92
|
+
ref={touchRef}
|
|
93
|
+
style={wrapperStyle}
|
|
94
|
+
onPress={onPressUnlessTyping}
|
|
95
|
+
onLongPress={onLongPressUnlessTyping}
|
|
96
|
+
focusable={allowFocus || focused}
|
|
97
|
+
onFocus={() => { changeFocus(true) }}
|
|
98
|
+
onBlur={() => { changeFocus(false) }}
|
|
99
|
+
hasTVPreferredFocus={props.shouldFocus && !Keyboard.isVisible}
|
|
100
|
+
disabled={props.disabled}>
|
|
101
|
+
<SnowText noSelect style={textStyle}>{props.title}</SnowText>
|
|
102
|
+
</Pressable >
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export default SnowTextButton
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { View, Text } from 'react-native'
|
|
2
|
+
import { useStyleContext } from '../context/snow-style-context'
|
|
3
|
+
|
|
4
|
+
export function SnowText(props) {
|
|
5
|
+
const { SnowStyle } = useStyleContext(props)
|
|
6
|
+
|
|
7
|
+
let style = [SnowStyle.component.text.text]
|
|
8
|
+
if (!props.shrink && !props.skipDefault) {
|
|
9
|
+
style.push(SnowStyle.component.text.normal)
|
|
10
|
+
}
|
|
11
|
+
if (props.noSelect) {
|
|
12
|
+
style.push(SnowStyle.component.text.noSelect)
|
|
13
|
+
}
|
|
14
|
+
if (props.style) {
|
|
15
|
+
style.push(props.style)
|
|
16
|
+
}
|
|
17
|
+
let wrapperStyle = null
|
|
18
|
+
if (props.center) {
|
|
19
|
+
wrapperStyle = SnowStyle.component.text.center
|
|
20
|
+
}
|
|
21
|
+
return (
|
|
22
|
+
<View style={wrapperStyle}>
|
|
23
|
+
<Text
|
|
24
|
+
style={style}
|
|
25
|
+
selectable={!props.noSelect}
|
|
26
|
+
children={props.children} />
|
|
27
|
+
</View>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default SnowText
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Switch, Pressable } from 'react-native'
|
|
2
|
+
import { useStyleContext } from '../context/snow-style-context'
|
|
3
|
+
import { SnowLabel } from './snow-label'
|
|
4
|
+
|
|
5
|
+
/* spread props
|
|
6
|
+
nextFocusLeft
|
|
7
|
+
nextFocusRight
|
|
8
|
+
nextFocusUp
|
|
9
|
+
nextFocusDown
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export function SnowToggle(props) {
|
|
13
|
+
const { SnowStyle } = useStyleContext(props)
|
|
14
|
+
|
|
15
|
+
const toggleValue = () => {
|
|
16
|
+
props.onValueChange(!props.value)
|
|
17
|
+
}
|
|
18
|
+
return (
|
|
19
|
+
<Pressable
|
|
20
|
+
{...props}
|
|
21
|
+
onPress={toggleValue}
|
|
22
|
+
style={SnowStyle.component.toggle.center}>
|
|
23
|
+
<SnowLabel>{props.title}</SnowLabel>
|
|
24
|
+
<Switch
|
|
25
|
+
style={{ marginLeft: 'auto', marginRight: 'auto' }}
|
|
26
|
+
value={props.value}
|
|
27
|
+
onValueChange={toggleValue} />
|
|
28
|
+
</Pressable>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default SnowToggle
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Platform } from 'react-native'
|
|
3
|
+
import { SnowSafeArea } from '../component/snow-safe-area'
|
|
4
|
+
|
|
5
|
+
const FocusContext = React.createContext({});
|
|
6
|
+
|
|
7
|
+
export function useFocusContext() {
|
|
8
|
+
const value = React.useContext(FocusContext);
|
|
9
|
+
if (!value) {
|
|
10
|
+
throw new Error('useFocusContext must be wrapped in a <FocusContextProvider />');
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function FocusContextProvider(props) {
|
|
16
|
+
const [lockedElement, setLockedElement] = React.useState(null)
|
|
17
|
+
const focusIsLocked = !!lockedElement
|
|
18
|
+
const allowFocusing = Platform.isTV
|
|
19
|
+
|
|
20
|
+
const focusContext = {
|
|
21
|
+
allowFocusing,
|
|
22
|
+
lockedElement,
|
|
23
|
+
setLockedElement,
|
|
24
|
+
focusIsLocked
|
|
25
|
+
}
|
|
26
|
+
return (
|
|
27
|
+
<FocusContext.Provider
|
|
28
|
+
style={{ flex: 1 }}
|
|
29
|
+
value={focusContext}>
|
|
30
|
+
<SnowSafeArea style={{ flex: 1 }} >
|
|
31
|
+
{props.children}
|
|
32
|
+
</SnowSafeArea>
|
|
33
|
+
</FocusContext.Provider>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default FocusContextProvider
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import _ from 'lodash'
|
|
3
|
+
import { createStyle, getWindowHeight, getWindowWidth } from '../snow-style'
|
|
4
|
+
|
|
5
|
+
const StyleContext = React.createContext({});
|
|
6
|
+
|
|
7
|
+
export function useStyleContext(componentProps) {
|
|
8
|
+
let value = React.useContext(StyleContext);
|
|
9
|
+
if (!value) {
|
|
10
|
+
throw new Error('useStyleContext must be wrapped in a <StyleContextProvider />');
|
|
11
|
+
}
|
|
12
|
+
if (componentProps && componentProps.snowStyle) {
|
|
13
|
+
value = { ...value }
|
|
14
|
+
value.SnowStyle = _.merge({}, value.SnowStyle, componentProps.snowStyle)
|
|
15
|
+
}
|
|
16
|
+
if (componentProps && componentProps.snowConfig) {
|
|
17
|
+
value = { ...value }
|
|
18
|
+
value.SnowConfig = _.merge({}, value.SnowConfig, componentProps.snowConfig)
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const AppConfig = {
|
|
24
|
+
inputDebounceMilliseconds: 700
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function StyleContextProvider(props) {
|
|
28
|
+
let style = createStyle(props.snowStyle)
|
|
29
|
+
let config = AppConfig
|
|
30
|
+
if (props.snowConfig) {
|
|
31
|
+
config = { ...config, ...props.snowConfig }
|
|
32
|
+
}
|
|
33
|
+
const context = {
|
|
34
|
+
SnowStyle: style,
|
|
35
|
+
SnowConfig: config,
|
|
36
|
+
getWindowHeight,
|
|
37
|
+
getWindowWidth,
|
|
38
|
+
}
|
|
39
|
+
return (
|
|
40
|
+
<StyleContext.Provider style={{ flex: 1 }} value={context}>
|
|
41
|
+
{props.children}
|
|
42
|
+
</StyleContext.Provider>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default StyleContextProvider
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
export { SnowApp } from './snow-app'
|
|
3
|
+
import { SnowApp } from './snow-app'
|
|
4
|
+
|
|
5
|
+
export { useFocusContext } from './context/snow-focus-context'
|
|
6
|
+
import { useFocusContext } from './context/snow-focus-context'
|
|
7
|
+
export { useStyleContext } from './context/snow-style-context'
|
|
8
|
+
import { useStyleContext } from './context/snow-style-context'
|
|
9
|
+
|
|
10
|
+
export { SnowBreak } from './component/snow-break'
|
|
11
|
+
import { SnowBreak } from './component/snow-break'
|
|
12
|
+
export { SnowDropdown } from './component/snow-dropdown'
|
|
13
|
+
import { SnowDropdown } from './component/snow-dropdown'
|
|
14
|
+
export { SnowFillView } from './component/snow-fill-view'
|
|
15
|
+
import { SnowFillView } from './component/snow-fill-view'
|
|
16
|
+
export { SnowGrid } from './component/snow-grid'
|
|
17
|
+
import { SnowGrid } from './component/snow-grid'
|
|
18
|
+
export { SnowHeader } from './component/snow-header'
|
|
19
|
+
import { SnowHeader } from './component/snow-header'
|
|
20
|
+
export { SnowImageButton } from './component/snow-image-button'
|
|
21
|
+
import { SnowImageButton } from './component/snow-image-button'
|
|
22
|
+
export { SnowImageGrid } from './component/snow-image-grid'
|
|
23
|
+
import { SnowImageGrid } from './component/snow-image-grid'
|
|
24
|
+
export { SnowInput } from './component/snow-input'
|
|
25
|
+
import { SnowInput } from './component/snow-input'
|
|
26
|
+
export { SnowLabel } from './component/snow-label'
|
|
27
|
+
import { SnowLabel } from './component/snow-label'
|
|
28
|
+
export { SnowModal } from './component/snow-modal'
|
|
29
|
+
import { SnowModal } from './component/snow-modal'
|
|
30
|
+
export { SnowRangeSlider } from './component/snow-range-slider'
|
|
31
|
+
import { SnowRangeSlider } from './component/snow-range-slider'
|
|
32
|
+
export { SnowSafeArea } from './component/snow-safe-area'
|
|
33
|
+
import { SnowSafeArea } from './component/snow-safe-area'
|
|
34
|
+
export { SnowTabs } from './component/snow-tabs'
|
|
35
|
+
import { SnowTabs } from './component/snow-tabs'
|
|
36
|
+
export { SnowTextButton } from './component/snow-text-button'
|
|
37
|
+
import { SnowTextButton } from './component/snow-text-button'
|
|
38
|
+
export { SnowText } from './component/snow-text'
|
|
39
|
+
import { SnowText } from './component/snow-text'
|
|
40
|
+
export { SnowToggle } from './component/snow-toggle'
|
|
41
|
+
import { SnowToggle } from './component/snow-toggle'
|
|
42
|
+
|
|
43
|
+
export default {
|
|
44
|
+
useFocusContext,
|
|
45
|
+
useStyleContext,
|
|
46
|
+
App: SnowApp,
|
|
47
|
+
Break: SnowBreak,
|
|
48
|
+
Dropdown: SnowDropdown,
|
|
49
|
+
FillView: SnowFillView,
|
|
50
|
+
Grid: SnowGrid,
|
|
51
|
+
Header: SnowHeader,
|
|
52
|
+
ImageButton: SnowImageButton,
|
|
53
|
+
ImageGrid: SnowImageGrid,
|
|
54
|
+
Input: SnowInput,
|
|
55
|
+
Label: SnowLabel,
|
|
56
|
+
Modal: SnowModal,
|
|
57
|
+
RangeSlider: SnowRangeSlider,
|
|
58
|
+
SafeArea: SnowSafeArea,
|
|
59
|
+
Tabs: SnowTabs,
|
|
60
|
+
TextButton: SnowTextButton,
|
|
61
|
+
Text: SnowText,
|
|
62
|
+
Toggle: SnowToggle,
|
|
63
|
+
}
|
package/src/snow-app.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { View, ScrollView, Platform, Dimensions } from 'react-native'
|
|
2
|
+
import { StyleContextProvider, useStyleContext } from './context/snow-style-context'
|
|
3
|
+
import { FocusContextProvider } from './context/snow-focus-context'
|
|
4
|
+
|
|
5
|
+
export function SnowApp(props) {
|
|
6
|
+
const { SnowStyle } = useStyleContext(props)
|
|
7
|
+
let rootInnerStyle = []
|
|
8
|
+
if (Platform.OS === 'web') {
|
|
9
|
+
rootInnerStyle.push({
|
|
10
|
+
height: Dimensions.get('window').height,
|
|
11
|
+
backgroundColor: 'black'
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
return (
|
|
15
|
+
<ScrollView style={{ flex: 1, backgroundColor: 'black' }} contentContainerStyle={rootInnerStyle}>
|
|
16
|
+
<StyleContextProvider snowStyle={props.snowStyle} snowConfig={props.snowConfig}>
|
|
17
|
+
<FocusContextProvider>
|
|
18
|
+
<View style={{ flex: 1, marginBottom: 50 }}>
|
|
19
|
+
{props.children}
|
|
20
|
+
</View>
|
|
21
|
+
</FocusContextProvider>
|
|
22
|
+
</StyleContextProvider>
|
|
23
|
+
</ScrollView>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default SnowApp
|