@butternutbox/pawprint-native 0.10.5 → 0.10.7
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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +13 -0
- package/dist/index.cjs +153 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.js +153 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/atoms/Illustration/Illustration.stories.tsx +3 -1
- package/src/components/atoms/Input/InputField.tsx +9 -1
- package/src/components/atoms/NumberInput/NumberInput.stories.tsx +22 -0
- package/src/components/atoms/NumberInput/NumberInput.tsx +5 -1
- package/src/components/atoms/NumberInput/NumberInputField.tsx +81 -18
- package/src/components/atoms/Typography/Typography.test.tsx +2 -2
- package/src/components/molecules/Drawer/Drawer.stories.tsx +58 -0
- package/src/components/molecules/Drawer/Drawer.tsx +10 -3
- package/src/components/molecules/Drawer/index.ts +5 -1
- package/src/components/molecules/NumberField/NumberField.stories.tsx +23 -0
- package/src/components/molecules/NumberField/NumberFieldInput.tsx +49 -14
package/package.json
CHANGED
|
@@ -10,6 +10,7 @@ import * as poses from "@butternutbox/pawprint-illustrations/poses"
|
|
|
10
10
|
import * as usps from "@butternutbox/pawprint-illustrations/usps"
|
|
11
11
|
import * as breeds from "@butternutbox/pawprint-illustrations/breeds"
|
|
12
12
|
import * as breedSizes from "@butternutbox/pawprint-illustrations/breed-sizes"
|
|
13
|
+
import * as traits from "@butternutbox/pawprint-illustrations/traits"
|
|
13
14
|
|
|
14
15
|
function isComponent(value: unknown): value is PawprintIllustration {
|
|
15
16
|
if (typeof value === "function") return true
|
|
@@ -21,7 +22,8 @@ const categorised: Record<string, Record<string, unknown>> = {
|
|
|
21
22
|
poses,
|
|
22
23
|
usps,
|
|
23
24
|
breeds,
|
|
24
|
-
"breed-sizes": breedSizes
|
|
25
|
+
"breed-sizes": breedSizes,
|
|
26
|
+
traits
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
const illustrationMap: Record<string, PawprintIllustration> = {}
|
|
@@ -26,6 +26,7 @@ type InputFieldOwnProps = {
|
|
|
26
26
|
hideStateIcons?: boolean
|
|
27
27
|
containerWidth?: number
|
|
28
28
|
containerHeight?: number
|
|
29
|
+
hasInlineHelp?: boolean
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export type InputFieldProps = InputFieldOwnProps &
|
|
@@ -60,7 +61,10 @@ const StyledInputWrapper = styled(Animated.View)<{
|
|
|
60
61
|
}
|
|
61
62
|
})
|
|
62
63
|
|
|
63
|
-
const StyledInput = styled(TextInput)
|
|
64
|
+
const StyledInput = styled(TextInput)<{ hasInlineHelp?: boolean }>(({
|
|
65
|
+
theme,
|
|
66
|
+
hasInlineHelp
|
|
67
|
+
}) => {
|
|
64
68
|
const { colour, field } = theme.tokens.components.inputs
|
|
65
69
|
const typography = field.placeholder.default
|
|
66
70
|
const { fontFamily } = resolveFont(
|
|
@@ -71,6 +75,7 @@ const StyledInput = styled(TextInput)(({ theme }) => {
|
|
|
71
75
|
const letterSpacing = typography.letterSpacing?.endsWith("%")
|
|
72
76
|
? (parseFloat(typography.letterSpacing) / 100) * fontSize
|
|
73
77
|
: parseFloat(typography.letterSpacing ?? "0")
|
|
78
|
+
|
|
74
79
|
return {
|
|
75
80
|
flex: 1,
|
|
76
81
|
minWidth: 0,
|
|
@@ -80,6 +85,7 @@ const StyledInput = styled(TextInput)(({ theme }) => {
|
|
|
80
85
|
fontFamily,
|
|
81
86
|
fontSize,
|
|
82
87
|
letterSpacing,
|
|
88
|
+
...(hasInlineHelp ? { position: "relative", top: -8 } : {}),
|
|
83
89
|
// Suppress the browser default focus outline on React Native Web —
|
|
84
90
|
// the focus indicator is rendered on the wrapper instead.
|
|
85
91
|
...({
|
|
@@ -120,6 +126,7 @@ export const InputField = React.forwardRef<TextInput, InputFieldProps>(
|
|
|
120
126
|
hideStateIcons,
|
|
121
127
|
containerWidth,
|
|
122
128
|
containerHeight,
|
|
129
|
+
hasInlineHelp,
|
|
123
130
|
style,
|
|
124
131
|
editable,
|
|
125
132
|
onFocus,
|
|
@@ -270,6 +277,7 @@ export const InputField = React.forwardRef<TextInput, InputFieldProps>(
|
|
|
270
277
|
editable={editable}
|
|
271
278
|
onFocus={handleFocus}
|
|
272
279
|
onBlur={handleBlur}
|
|
280
|
+
hasInlineHelp={hasInlineHelp}
|
|
273
281
|
{...rest}
|
|
274
282
|
/>
|
|
275
283
|
{trailingIcon && <StyledIconSlot>{trailingIcon}</StyledIconSlot>}
|
|
@@ -31,6 +31,10 @@ export default {
|
|
|
31
31
|
fieldText: {
|
|
32
32
|
control: { type: "text" },
|
|
33
33
|
description: "Unit label shown to the right of the input (e.g. kg, lbs)"
|
|
34
|
+
},
|
|
35
|
+
inlineHelpText: {
|
|
36
|
+
control: { type: "text" },
|
|
37
|
+
description: "Help text displayed below the input field"
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
}
|
|
@@ -155,6 +159,24 @@ export const CustomStateValidationWithSuccess = () => {
|
|
|
155
159
|
)
|
|
156
160
|
}
|
|
157
161
|
|
|
162
|
+
export const WithInlineHelpText = () => (
|
|
163
|
+
<View style={styles.column}>
|
|
164
|
+
<NumberInput
|
|
165
|
+
label="Daily amount"
|
|
166
|
+
placeholder="0"
|
|
167
|
+
inlineHelpText="~ 840 kcals"
|
|
168
|
+
description="Help text"
|
|
169
|
+
/>
|
|
170
|
+
<NumberInput
|
|
171
|
+
label="Quantity"
|
|
172
|
+
placeholder="0"
|
|
173
|
+
inlineHelpText="per serving"
|
|
174
|
+
fieldText="portions"
|
|
175
|
+
description="Additional context"
|
|
176
|
+
/>
|
|
177
|
+
</View>
|
|
178
|
+
)
|
|
179
|
+
|
|
158
180
|
export const Controlled = () => {
|
|
159
181
|
const [value, setValue] = React.useState("")
|
|
160
182
|
|
|
@@ -13,6 +13,7 @@ type NumberInputOwnProps = Pick<
|
|
|
13
13
|
"label" | "description" | "error" | "state" | "optionalText" | "onValueChange"
|
|
14
14
|
> & {
|
|
15
15
|
fieldText?: string
|
|
16
|
+
inlineHelpText?: string
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export type NumberInputProps = NumberInputOwnProps &
|
|
@@ -60,7 +61,8 @@ const StyledRoot = styled(View)(({ theme }) => {
|
|
|
60
61
|
* </NumberInput.Root>
|
|
61
62
|
*
|
|
62
63
|
* @param {string} [label] - Label text (props API)
|
|
63
|
-
* @param {string} [description] - Description/help text (props API)
|
|
64
|
+
* @param {string} [description] - Description/help text below the field (props API)
|
|
65
|
+
* @param {string} [inlineHelpText] - Help text displayed below the input field (props API)
|
|
64
66
|
* @param {string} [error] - Error message to display (props API, does not affect visual state - use state prop for that)
|
|
65
67
|
* @param {InputState} [state] - Visual state of the input: 'default', 'error', or 'success' (props API)
|
|
66
68
|
* @param {string} [optionalText] - Optional text to display next to label (props API)
|
|
@@ -77,6 +79,7 @@ const NumberInputRoot = React.forwardRef<View, NumberInputProps>(
|
|
|
77
79
|
optionalText,
|
|
78
80
|
onValueChange,
|
|
79
81
|
fieldText,
|
|
82
|
+
inlineHelpText,
|
|
80
83
|
children,
|
|
81
84
|
...inputProps
|
|
82
85
|
},
|
|
@@ -97,6 +100,7 @@ const NumberInputRoot = React.forwardRef<View, NumberInputProps>(
|
|
|
97
100
|
state={state}
|
|
98
101
|
onChangeText={onValueChange}
|
|
99
102
|
fieldText={fieldText}
|
|
103
|
+
inlineHelpText={inlineHelpText}
|
|
100
104
|
{...inputProps}
|
|
101
105
|
/>
|
|
102
106
|
{description && (
|
|
@@ -21,8 +21,16 @@ const StyledFieldTextWrapper = styled(View)({
|
|
|
21
21
|
justifyContent: "center"
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
+
const StyledInputContainer = styled(View)<{
|
|
25
|
+
hasInlineHelp?: boolean
|
|
26
|
+
}>(({ hasInlineHelp }) => ({
|
|
27
|
+
flexDirection: "column",
|
|
28
|
+
gap: hasInlineHelp ? 2 : 0
|
|
29
|
+
}))
|
|
30
|
+
|
|
24
31
|
type NumberInputFieldOwnProps = {
|
|
25
32
|
fieldText?: string
|
|
33
|
+
inlineHelpText?: string
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
export type NumberInputFieldProps = NumberInputFieldOwnProps &
|
|
@@ -36,7 +44,7 @@ export type NumberInputFieldProps = NumberInputFieldOwnProps &
|
|
|
36
44
|
export const NumberInputField = React.forwardRef<
|
|
37
45
|
TextInput,
|
|
38
46
|
NumberInputFieldProps
|
|
39
|
-
>(({ fieldText, ...props }, ref) => {
|
|
47
|
+
>(({ fieldText, inlineHelpText, ...props }, ref) => {
|
|
40
48
|
const theme = useTheme()
|
|
41
49
|
const { colour, description } = theme.tokens.components.inputs
|
|
42
50
|
const fieldSize = parseTokenValue(
|
|
@@ -48,30 +56,85 @@ export const NumberInputField = React.forwardRef<
|
|
|
48
56
|
keyboardType="numeric"
|
|
49
57
|
hideStateIcons
|
|
50
58
|
containerWidth={fieldSize}
|
|
51
|
-
containerHeight={fieldSize}
|
|
59
|
+
containerHeight={inlineHelpText ? undefined : fieldSize}
|
|
60
|
+
hasInlineHelp={!!inlineHelpText}
|
|
52
61
|
ref={ref}
|
|
53
62
|
style={{ textAlign: "center" }}
|
|
54
63
|
{...props}
|
|
55
64
|
/>
|
|
56
65
|
)
|
|
57
66
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
const fieldContent =
|
|
68
|
+
inlineHelpText && fieldText ? (
|
|
69
|
+
<StyledFieldGroup>
|
|
70
|
+
<StyledInputContainer hasInlineHelp>
|
|
71
|
+
{inputElement}
|
|
72
|
+
<View
|
|
73
|
+
style={{
|
|
74
|
+
position: "absolute",
|
|
75
|
+
bottom: 4,
|
|
76
|
+
left: 0,
|
|
77
|
+
width: fieldSize,
|
|
78
|
+
display: "flex",
|
|
79
|
+
justifyContent: "center",
|
|
80
|
+
alignItems: "center"
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
<Typography
|
|
84
|
+
token={description.text.default}
|
|
85
|
+
color={colour.description.default}
|
|
86
|
+
>
|
|
87
|
+
{inlineHelpText}
|
|
88
|
+
</Typography>
|
|
89
|
+
</View>
|
|
90
|
+
</StyledInputContainer>
|
|
91
|
+
<StyledFieldTextWrapper>
|
|
92
|
+
<Typography
|
|
93
|
+
token={description.text.default}
|
|
94
|
+
color={colour.description.default}
|
|
95
|
+
>
|
|
96
|
+
{fieldText}
|
|
97
|
+
</Typography>
|
|
98
|
+
</StyledFieldTextWrapper>
|
|
99
|
+
</StyledFieldGroup>
|
|
100
|
+
) : fieldText ? (
|
|
101
|
+
<StyledFieldGroup>
|
|
102
|
+
{inputElement}
|
|
103
|
+
<StyledFieldTextWrapper>
|
|
104
|
+
<Typography
|
|
105
|
+
token={description.text.default}
|
|
106
|
+
color={colour.description.default}
|
|
107
|
+
>
|
|
108
|
+
{fieldText}
|
|
109
|
+
</Typography>
|
|
110
|
+
</StyledFieldTextWrapper>
|
|
111
|
+
</StyledFieldGroup>
|
|
112
|
+
) : inlineHelpText ? (
|
|
113
|
+
<StyledInputContainer hasInlineHelp>
|
|
114
|
+
{inputElement}
|
|
115
|
+
<View
|
|
116
|
+
style={{
|
|
117
|
+
position: "absolute",
|
|
118
|
+
bottom: 4,
|
|
119
|
+
width: fieldSize,
|
|
120
|
+
display: "flex",
|
|
121
|
+
justifyContent: "center",
|
|
122
|
+
alignItems: "center"
|
|
123
|
+
}}
|
|
69
124
|
>
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
125
|
+
<Typography
|
|
126
|
+
token={description.text.default}
|
|
127
|
+
color={colour.description.default}
|
|
128
|
+
>
|
|
129
|
+
{inlineHelpText}
|
|
130
|
+
</Typography>
|
|
131
|
+
</View>
|
|
132
|
+
</StyledInputContainer>
|
|
133
|
+
) : (
|
|
134
|
+
inputElement
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return fieldContent
|
|
75
138
|
})
|
|
76
139
|
|
|
77
140
|
NumberInputField.displayName = "NumberInput.Field"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react"
|
|
2
2
|
import { screen } from "@testing-library/react"
|
|
3
3
|
import { describe, it, expect } from "vitest"
|
|
4
|
-
import { renderWithTheme } from "../../../test-utils"
|
|
4
|
+
import { renderWithTheme, theme } from "../../../test-utils"
|
|
5
5
|
import { Typography } from "./Typography"
|
|
6
6
|
|
|
7
7
|
describe("Typography", () => {
|
|
@@ -64,7 +64,7 @@ describe("Typography", () => {
|
|
|
64
64
|
fontSize: "16",
|
|
65
65
|
lineHeight: "24"
|
|
66
66
|
}}
|
|
67
|
-
color=
|
|
67
|
+
color={theme.tokens.semantics.colour.text.primary}
|
|
68
68
|
>
|
|
69
69
|
Token text
|
|
70
70
|
</Typography>
|
|
@@ -273,6 +273,54 @@ Playground.args = {
|
|
|
273
273
|
defaultOpen: false
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
+
export const WithTopContent = () => {
|
|
277
|
+
const [showToast, setShowToast] = useState(false)
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<View style={styles.container}>
|
|
281
|
+
<Drawer.Root>
|
|
282
|
+
<Drawer.Trigger>
|
|
283
|
+
<Button variant="filled" colour="primary">
|
|
284
|
+
Open drawer with overlay
|
|
285
|
+
</Button>
|
|
286
|
+
</Drawer.Trigger>
|
|
287
|
+
<Drawer.Portal
|
|
288
|
+
topContent={
|
|
289
|
+
showToast ? (
|
|
290
|
+
<View style={styles.toast}>
|
|
291
|
+
<Typography>Action saved!</Typography>
|
|
292
|
+
</View>
|
|
293
|
+
) : null
|
|
294
|
+
}
|
|
295
|
+
>
|
|
296
|
+
<Drawer.Overlay />
|
|
297
|
+
<Drawer.Content>
|
|
298
|
+
<Drawer.Header variant="titleAndText">
|
|
299
|
+
<Drawer.Title>Floating content demo</Drawer.Title>
|
|
300
|
+
<Drawer.Close />
|
|
301
|
+
</Drawer.Header>
|
|
302
|
+
<Drawer.Body>
|
|
303
|
+
<Typography>{bodyText}</Typography>
|
|
304
|
+
</Drawer.Body>
|
|
305
|
+
<Drawer.Footer>
|
|
306
|
+
<Button
|
|
307
|
+
variant="filled"
|
|
308
|
+
colour="primary"
|
|
309
|
+
onPress={() => {
|
|
310
|
+
setShowToast(true)
|
|
311
|
+
setTimeout(() => setShowToast(false), 2000)
|
|
312
|
+
}}
|
|
313
|
+
>
|
|
314
|
+
Show overlay
|
|
315
|
+
</Button>
|
|
316
|
+
</Drawer.Footer>
|
|
317
|
+
</Drawer.Content>
|
|
318
|
+
</Drawer.Portal>
|
|
319
|
+
</Drawer.Root>
|
|
320
|
+
</View>
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
|
|
276
324
|
const styles = StyleSheet.create({
|
|
277
325
|
container: {
|
|
278
326
|
padding: 16
|
|
@@ -281,5 +329,15 @@ const styles = StyleSheet.create({
|
|
|
281
329
|
flexDirection: "row",
|
|
282
330
|
gap: 12,
|
|
283
331
|
marginBottom: 16
|
|
332
|
+
},
|
|
333
|
+
toast: {
|
|
334
|
+
position: "absolute",
|
|
335
|
+
top: 60,
|
|
336
|
+
left: 16,
|
|
337
|
+
right: 16,
|
|
338
|
+
backgroundColor: "#000",
|
|
339
|
+
padding: 16,
|
|
340
|
+
borderRadius: 8,
|
|
341
|
+
zIndex: 100
|
|
284
342
|
}
|
|
285
343
|
})
|
|
@@ -135,15 +135,19 @@ DrawerTrigger.displayName = "Drawer.Trigger"
|
|
|
135
135
|
|
|
136
136
|
// ─── Portal ───────────────────────────────────────────────────────────────────
|
|
137
137
|
|
|
138
|
-
type DrawerPortalProps = {
|
|
138
|
+
export type DrawerPortalProps = {
|
|
139
139
|
children: React.ReactNode
|
|
140
|
+
topContent?: React.ReactNode
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
/**
|
|
143
144
|
* Renders its children above the rest of the app using React Native `Modal`.
|
|
144
145
|
* The modal stays mounted during exit animations and unmounts once complete.
|
|
146
|
+
*
|
|
147
|
+
* @param children - Drawer content (Overlay, Content, etc.)
|
|
148
|
+
* @param topContent - *(optional)* Additional content rendered on top of the drawer
|
|
145
149
|
*/
|
|
146
|
-
const DrawerPortal = ({ children }: DrawerPortalProps) => {
|
|
150
|
+
const DrawerPortal = ({ children, topContent }: DrawerPortalProps) => {
|
|
147
151
|
const { modalVisible, closeDrawer } = React.useContext(DrawerContext)
|
|
148
152
|
|
|
149
153
|
if (!modalVisible) return null
|
|
@@ -156,7 +160,10 @@ const DrawerPortal = ({ children }: DrawerPortalProps) => {
|
|
|
156
160
|
statusBarTranslucent
|
|
157
161
|
onRequestClose={closeDrawer}
|
|
158
162
|
>
|
|
159
|
-
<View style={styles.modalContainer}>
|
|
163
|
+
<View style={styles.modalContainer}>
|
|
164
|
+
{children}
|
|
165
|
+
{topContent}
|
|
166
|
+
</View>
|
|
160
167
|
</Modal>
|
|
161
168
|
)
|
|
162
169
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export { Drawer } from "./Drawer"
|
|
2
|
-
export type {
|
|
2
|
+
export type {
|
|
3
|
+
DrawerProps,
|
|
4
|
+
DrawerTriggerProps,
|
|
5
|
+
DrawerPortalProps
|
|
6
|
+
} from "./Drawer"
|
|
3
7
|
export type { DrawerContentProps } from "./DrawerContent"
|
|
4
8
|
export type { DrawerOverlayProps } from "./DrawerOverlay"
|
|
5
9
|
export type { DrawerGrabberProps } from "./DrawerGrabber"
|
|
@@ -25,6 +25,10 @@ export default {
|
|
|
25
25
|
control: { type: "text" },
|
|
26
26
|
description: "Help text displayed below the field"
|
|
27
27
|
},
|
|
28
|
+
inlineHelpText: {
|
|
29
|
+
control: { type: "text" },
|
|
30
|
+
description: "Help text displayed inside the input field"
|
|
31
|
+
},
|
|
28
32
|
error: {
|
|
29
33
|
control: { type: "text" },
|
|
30
34
|
description: "Error message when state is error"
|
|
@@ -221,6 +225,25 @@ export const FullWidth = () => {
|
|
|
221
225
|
)
|
|
222
226
|
}
|
|
223
227
|
|
|
228
|
+
export const WithInlineHelpText = () => {
|
|
229
|
+
const [value, setValue] = useState(12)
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
<View style={styles.section}>
|
|
233
|
+
<NumberField
|
|
234
|
+
label="Daily amount"
|
|
235
|
+
size="lg"
|
|
236
|
+
inlineHelpText="~ 840 kcals"
|
|
237
|
+
description="Help text"
|
|
238
|
+
value={String(value)}
|
|
239
|
+
onChangeText={(t) => setValue(Number(t) || 0)}
|
|
240
|
+
onIncrement={() => setValue((v) => v + 1)}
|
|
241
|
+
onDecrement={() => setValue((v) => Math.max(0, v - 1))}
|
|
242
|
+
/>
|
|
243
|
+
</View>
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
|
|
224
247
|
export const Playground = {
|
|
225
248
|
args: {
|
|
226
249
|
size: "lg",
|
|
@@ -5,6 +5,7 @@ import { useTheme } from "@emotion/react"
|
|
|
5
5
|
import { IconButton } from "../../atoms/IconButton"
|
|
6
6
|
import { Add, Remove } from "@butternutbox/pawprint-icons/core"
|
|
7
7
|
import { resolveFont } from "../../../fonts"
|
|
8
|
+
import { Typography } from "../../atoms/Typography"
|
|
8
9
|
|
|
9
10
|
type NumberFieldSize = "sm" | "lg"
|
|
10
11
|
|
|
@@ -21,6 +22,7 @@ type NumberFieldInputOwnProps = {
|
|
|
21
22
|
decrementDisabled?: boolean
|
|
22
23
|
min?: number
|
|
23
24
|
max?: number
|
|
25
|
+
inlineHelpText?: string
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export type NumberFieldInputProps = NumberFieldInputOwnProps &
|
|
@@ -48,6 +50,7 @@ const StyledFieldWrapper = styled(View)<{
|
|
|
48
50
|
}) => ({
|
|
49
51
|
alignItems: "center",
|
|
50
52
|
justifyContent: "center",
|
|
53
|
+
position: "relative",
|
|
51
54
|
width: fullWidth ? "100%" : fieldMinWidth,
|
|
52
55
|
minWidth: fieldMinWidth,
|
|
53
56
|
height: fieldHeight,
|
|
@@ -79,20 +82,29 @@ const StyledTextInput = styled(TextInput)<{
|
|
|
79
82
|
inputFontFamily: string
|
|
80
83
|
inputFontWeight?: FontWeight
|
|
81
84
|
inputFontSize: number
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
85
|
+
hasInlineHelp?: boolean
|
|
86
|
+
}>(
|
|
87
|
+
({
|
|
88
|
+
inputColor,
|
|
89
|
+
inputFontFamily,
|
|
90
|
+
inputFontWeight,
|
|
91
|
+
inputFontSize,
|
|
92
|
+
hasInlineHelp
|
|
93
|
+
}) => ({
|
|
94
|
+
textAlign: "center" as const,
|
|
95
|
+
outlineStyle: "none" as unknown as undefined,
|
|
96
|
+
padding: 0,
|
|
97
|
+
minWidth: 0,
|
|
98
|
+
minHeight: 0,
|
|
99
|
+
width: "100%",
|
|
100
|
+
height: "100%",
|
|
101
|
+
color: inputColor,
|
|
102
|
+
fontFamily: inputFontFamily,
|
|
103
|
+
...(inputFontWeight ? { fontWeight: inputFontWeight } : {}),
|
|
104
|
+
fontSize: inputFontSize,
|
|
105
|
+
...(hasInlineHelp ? { position: "relative", top: -8 } : {})
|
|
106
|
+
})
|
|
107
|
+
)
|
|
96
108
|
|
|
97
109
|
const StyledRow = styled(View)<{
|
|
98
110
|
rowGap: number
|
|
@@ -107,6 +119,8 @@ const StyledRow = styled(View)<{
|
|
|
107
119
|
* Renders decrement button, centered numeric input, increment button,
|
|
108
120
|
* and optional side text (e.g. "kg").
|
|
109
121
|
*
|
|
122
|
+
* @param {string} [inlineHelpText] - Help text displayed inside the input field at the bottom
|
|
123
|
+
*
|
|
110
124
|
* Border states:
|
|
111
125
|
* - default: `colour.field.border.default`
|
|
112
126
|
* - focused: `colour.field.border.selected`
|
|
@@ -131,6 +145,7 @@ export const NumberFieldInput = React.forwardRef<
|
|
|
131
145
|
decrementDisabled = false,
|
|
132
146
|
min,
|
|
133
147
|
max,
|
|
148
|
+
inlineHelpText,
|
|
134
149
|
style,
|
|
135
150
|
onFocus,
|
|
136
151
|
onBlur,
|
|
@@ -253,6 +268,7 @@ export const NumberFieldInput = React.forwardRef<
|
|
|
253
268
|
: undefined
|
|
254
269
|
}
|
|
255
270
|
inputFontSize={parseTokenValue(typographyToken.fontSize)}
|
|
271
|
+
hasInlineHelp={!!inlineHelpText}
|
|
256
272
|
keyboardType="numeric"
|
|
257
273
|
editable={!disabled}
|
|
258
274
|
onFocus={handleFocus}
|
|
@@ -263,6 +279,25 @@ export const NumberFieldInput = React.forwardRef<
|
|
|
263
279
|
style={style}
|
|
264
280
|
{...rest}
|
|
265
281
|
/>
|
|
282
|
+
{inlineHelpText && (
|
|
283
|
+
<View
|
|
284
|
+
style={{
|
|
285
|
+
position: "absolute",
|
|
286
|
+
bottom: 4,
|
|
287
|
+
width: fieldMinWidth,
|
|
288
|
+
display: "flex",
|
|
289
|
+
justifyContent: "center",
|
|
290
|
+
alignItems: "center"
|
|
291
|
+
}}
|
|
292
|
+
>
|
|
293
|
+
<Typography
|
|
294
|
+
token={inputTokens.description.text.default}
|
|
295
|
+
color={inputTokens.colour.description.default}
|
|
296
|
+
>
|
|
297
|
+
{inlineHelpText}
|
|
298
|
+
</Typography>
|
|
299
|
+
</View>
|
|
300
|
+
)}
|
|
266
301
|
</StyledFieldWrapper>
|
|
267
302
|
{showIncrementButton && (
|
|
268
303
|
<IconButton
|