@butternutbox/pawprint-native 1.3.0 → 1.4.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/.turbo/turbo-build.log +9 -9
- package/CHANGELOG.md +23 -0
- package/dist/index.cjs +345 -106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -3
- package/dist/index.d.ts +62 -3
- package/dist/index.js +313 -77
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__mocks__/react-native.tsx +2 -0
- package/src/components/atoms/Button/Button.stories.tsx +4 -4
- package/src/components/atoms/Button/Button.test.tsx +9 -2
- package/src/components/atoms/Button/Button.tsx +39 -7
- package/src/components/molecules/NavigationHeader/NavigationHeader.stories.tsx +140 -0
- package/src/components/molecules/NavigationHeader/NavigationHeader.test.tsx +110 -0
- package/src/components/molecules/NavigationHeader/NavigationHeader.tsx +273 -0
- package/src/components/molecules/NavigationHeader/NavigationHeaderButton.tsx +87 -0
- package/src/components/molecules/NavigationHeader/NavigationHeaderContext.ts +38 -0
- package/src/components/molecules/NavigationHeader/index.ts +14 -0
- package/src/components/molecules/SelectField/SelectField.test.tsx +18 -0
- package/src/components/molecules/SelectField/SelectField.tsx +12 -0
- package/src/components/molecules/index.ts +1 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { View } from "react-native"
|
|
3
|
+
import { Button, type ButtonProps } from "../../atoms/Button"
|
|
4
|
+
import { IconButton, type IconButtonProps } from "../../atoms/IconButton"
|
|
5
|
+
import {
|
|
6
|
+
getButtonDefaults,
|
|
7
|
+
getIconButtonColour,
|
|
8
|
+
useNavigationHeaderContext
|
|
9
|
+
} from "./NavigationHeaderContext"
|
|
10
|
+
|
|
11
|
+
export type NavigationHeaderButtonProps = Omit<
|
|
12
|
+
ButtonProps,
|
|
13
|
+
"size" | "fullWidth"
|
|
14
|
+
>
|
|
15
|
+
|
|
16
|
+
export type NavigationHeaderIconButtonProps = Omit<IconButtonProps, "size">
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Text button for the navigation header action group. A medium `Button` with
|
|
20
|
+
* the header defaults: white `filled` / `tertiary` on the `brand` surface and
|
|
21
|
+
* `outlined` on other surfaces.
|
|
22
|
+
*
|
|
23
|
+
* Passing `variant` or `colour` overrides the default.
|
|
24
|
+
*
|
|
25
|
+
* @param {"filled" | "outlined" | "text"} [variant] - Button style.
|
|
26
|
+
* @param {"primary" | "secondary" | "tertiary"} [colour] - Filled colour.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* <NavigationHeader.Button onPress={editQuantity}>
|
|
31
|
+
* Pouch quantity
|
|
32
|
+
* </NavigationHeader.Button>
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export const NavigationHeaderButton = React.forwardRef<
|
|
36
|
+
View,
|
|
37
|
+
NavigationHeaderButtonProps
|
|
38
|
+
>(({ variant, colour, ...props }, ref) => {
|
|
39
|
+
const defaults = getButtonDefaults(useNavigationHeaderContext())
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Button
|
|
43
|
+
ref={ref}
|
|
44
|
+
size="md"
|
|
45
|
+
variant={variant ?? defaults.variant}
|
|
46
|
+
colour={colour ?? defaults.colour}
|
|
47
|
+
{...props}
|
|
48
|
+
/>
|
|
49
|
+
)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
NavigationHeaderButton.displayName = "NavigationHeader.Button"
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Icon button for the navigation header action group. A medium filled
|
|
56
|
+
* `IconButton`, `tertiary` on the `brand` surface and `secondary` on other
|
|
57
|
+
* surfaces.
|
|
58
|
+
*
|
|
59
|
+
* Passing `variant` or `colour` overrides the default.
|
|
60
|
+
*
|
|
61
|
+
* @param {PawprintIcon} icon - **(required)** Icon component.
|
|
62
|
+
* @param {string} aria-label - **(required)** Accessible label.
|
|
63
|
+
* @param {"filled" | "outlined" | "text"} [variant="filled"] - Button style.
|
|
64
|
+
* @param {"primary" | "secondary" | "tertiary"} [colour] - Filled colour.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```tsx
|
|
68
|
+
* <NavigationHeader.IconButton icon={Add} aria-label="Add dog" />
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export const NavigationHeaderIconButton = React.forwardRef<
|
|
72
|
+
View,
|
|
73
|
+
NavigationHeaderIconButtonProps
|
|
74
|
+
>(({ colour, ...props }, ref) => {
|
|
75
|
+
const context = useNavigationHeaderContext()
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<IconButton
|
|
79
|
+
ref={ref}
|
|
80
|
+
size="md"
|
|
81
|
+
colour={colour ?? getIconButtonColour(context)}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
NavigationHeaderIconButton.displayName = "NavigationHeader.IconButton"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { ButtonColour, ButtonVariant } from "../../atoms/Button"
|
|
3
|
+
import type { IconButtonColour } from "../../atoms/IconButton"
|
|
4
|
+
|
|
5
|
+
type NavigationHeaderSurface = "brand" | "subtle" | "plain"
|
|
6
|
+
|
|
7
|
+
type NavigationHeaderContextValue = {
|
|
8
|
+
surface: NavigationHeaderSurface
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const NavigationHeaderContext =
|
|
12
|
+
React.createContext<NavigationHeaderContextValue>({ surface: "brand" })
|
|
13
|
+
|
|
14
|
+
const useNavigationHeaderContext = () =>
|
|
15
|
+
React.useContext(NavigationHeaderContext)
|
|
16
|
+
|
|
17
|
+
const getButtonDefaults = ({
|
|
18
|
+
surface
|
|
19
|
+
}: NavigationHeaderContextValue): {
|
|
20
|
+
variant: ButtonVariant
|
|
21
|
+
colour: ButtonColour
|
|
22
|
+
} =>
|
|
23
|
+
surface === "brand"
|
|
24
|
+
? { variant: "filled", colour: "tertiary" }
|
|
25
|
+
: { variant: "outlined", colour: "primary" }
|
|
26
|
+
|
|
27
|
+
const getIconButtonColour = ({
|
|
28
|
+
surface
|
|
29
|
+
}: NavigationHeaderContextValue): IconButtonColour =>
|
|
30
|
+
surface === "brand" ? "tertiary" : "secondary"
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
NavigationHeaderContext,
|
|
34
|
+
useNavigationHeaderContext,
|
|
35
|
+
getButtonDefaults,
|
|
36
|
+
getIconButtonColour
|
|
37
|
+
}
|
|
38
|
+
export type { NavigationHeaderSurface, NavigationHeaderContextValue }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { NavigationHeader } from "./NavigationHeader"
|
|
2
|
+
export type {
|
|
3
|
+
NavigationHeaderProps,
|
|
4
|
+
NavigationHeaderVariant,
|
|
5
|
+
NavigationHeaderSurface
|
|
6
|
+
} from "./NavigationHeader"
|
|
7
|
+
export {
|
|
8
|
+
NavigationHeaderButton,
|
|
9
|
+
NavigationHeaderIconButton
|
|
10
|
+
} from "./NavigationHeaderButton"
|
|
11
|
+
export type {
|
|
12
|
+
NavigationHeaderButtonProps,
|
|
13
|
+
NavigationHeaderIconButtonProps
|
|
14
|
+
} from "./NavigationHeaderButton"
|
|
@@ -375,6 +375,24 @@ describe("SelectField", () => {
|
|
|
375
375
|
fireEvent.click(screen.getByTestId("select-overlay"))
|
|
376
376
|
expect(screen.queryByPlaceholderText("Search...")).not.toBeInTheDocument()
|
|
377
377
|
})
|
|
378
|
+
|
|
379
|
+
it("re-focuses the search input when it blurs while the dropdown is open", () => {
|
|
380
|
+
renderWithTheme(
|
|
381
|
+
<SelectField label="Country" placeholder="Select a country" searchable>
|
|
382
|
+
<SelectField.Item value="uk">United Kingdom</SelectField.Item>
|
|
383
|
+
</SelectField>
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
openDropdownWithContent()
|
|
387
|
+
const input = screen.getByPlaceholderText("Search...")
|
|
388
|
+
vi.useFakeTimers()
|
|
389
|
+
input.blur()
|
|
390
|
+
expect(input).not.toHaveFocus()
|
|
391
|
+
|
|
392
|
+
act(() => vi.advanceTimersByTime(50))
|
|
393
|
+
vi.useRealTimers()
|
|
394
|
+
expect(input).toHaveFocus()
|
|
395
|
+
})
|
|
378
396
|
})
|
|
379
397
|
|
|
380
398
|
describe("when searchable with an async query", () => {
|
|
@@ -315,6 +315,17 @@ const SelectFieldRoot = React.forwardRef<View, SelectFieldProps>(
|
|
|
315
315
|
// Re-running here re-focuses the input so typing is not interrupted.
|
|
316
316
|
}, [isOpen, searchable, isSearching, searchQuery])
|
|
317
317
|
|
|
318
|
+
const isOpenRef = React.useRef(isOpen)
|
|
319
|
+
React.useEffect(() => {
|
|
320
|
+
isOpenRef.current = isOpen
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
const handleSearchBlur = () => {
|
|
324
|
+
setTimeout(() => {
|
|
325
|
+
if (isOpenRef.current) searchInputRef.current?.focus()
|
|
326
|
+
}, 50)
|
|
327
|
+
}
|
|
328
|
+
|
|
318
329
|
const isCompound = React.Children.toArray(children).some(
|
|
319
330
|
(child) =>
|
|
320
331
|
React.isValidElement(child) && child.type === SelectFieldContent
|
|
@@ -447,6 +458,7 @@ const SelectFieldRoot = React.forwardRef<View, SelectFieldProps>(
|
|
|
447
458
|
ref={searchInputRef}
|
|
448
459
|
value={searchQuery}
|
|
449
460
|
onChangeText={setSearchQuery}
|
|
461
|
+
onBlur={handleSearchBlur}
|
|
450
462
|
placeholder={searchPlaceholder ?? "Search..."}
|
|
451
463
|
returnKeyType="search"
|
|
452
464
|
clearButtonMode="never"
|