@butternutbox/pawprint-native 0.0.1 → 0.1.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 +15 -15
- package/CHANGELOG.md +16 -0
- package/COMPONENT_GUIDELINES.md +111 -4
- package/dist/index.cjs +12370 -1455
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1110 -11
- package/dist/index.d.ts +1110 -11
- package/dist/index.js +12324 -1455
- package/dist/index.js.map +1 -1
- package/package.json +28 -9
- package/src/__mocks__/asset-stub.ts +1 -0
- package/src/__mocks__/emotion-native.tsx +18 -0
- package/src/__mocks__/react-native-gesture-handler.tsx +41 -0
- package/src/__mocks__/react-native-reanimated.tsx +79 -0
- package/src/__mocks__/react-native-safe-area-context.tsx +6 -0
- package/src/__mocks__/react-native-svg.tsx +27 -0
- package/src/__mocks__/react-native-worklets.tsx +11 -0
- package/src/__mocks__/react-native.tsx +338 -0
- package/src/__mocks__/rn-primitives/avatar.tsx +24 -0
- package/src/__mocks__/rn-primitives/checkbox.tsx +19 -0
- package/src/__mocks__/rn-primitives/select.tsx +116 -0
- package/src/__mocks__/rn-primitives/slider.tsx +40 -0
- package/src/__mocks__/rn-primitives/slot.tsx +30 -0
- package/src/__mocks__/rn-primitives/switch.tsx +24 -0
- package/src/__mocks__/rn-primitives/toggle.tsx +16 -0
- package/src/components/atoms/Avatar/Avatar.stories.tsx +57 -49
- package/src/components/atoms/Avatar/Avatar.test.tsx +269 -0
- package/src/components/atoms/Avatar/Avatar.tsx +68 -22
- package/src/components/atoms/Avatar/index.ts +1 -6
- package/src/components/atoms/Badge/Badge.stories.tsx +5 -29
- package/src/components/atoms/Badge/Badge.test.tsx +90 -0
- package/src/components/atoms/Button/Button.test.tsx +123 -0
- package/src/components/atoms/Button/Button.tsx +1 -1
- package/src/components/atoms/CarouselControls/CarouselControls.stories.tsx +217 -0
- package/src/components/atoms/CarouselControls/CarouselControls.tsx +127 -0
- package/src/components/atoms/CarouselControls/index.ts +2 -0
- package/src/components/atoms/Hint/Hint.test.tsx +36 -0
- package/src/components/atoms/Icon/Icon.test.tsx +98 -0
- package/src/components/atoms/Icon/Icon.tsx +5 -1
- package/src/components/atoms/IconButton/IconButton.test.tsx +101 -0
- package/src/components/atoms/Illustration/Illustration.test.tsx +55 -0
- package/src/components/atoms/Input/Input.stories.tsx +129 -86
- package/src/components/atoms/Input/Input.test.tsx +306 -0
- package/src/components/atoms/Input/Input.tsx +9 -1
- package/src/components/atoms/Input/InputField.tsx +226 -74
- package/src/components/atoms/Link/Link.test.tsx +89 -0
- package/src/components/atoms/Logo/Logo.registry.ts +30 -5
- package/src/components/atoms/Logo/Logo.stories.tsx +108 -0
- package/src/components/atoms/Logo/Logo.test.tsx +56 -0
- package/src/components/atoms/Logo/assets/BCorp.tsx +113 -0
- package/src/components/atoms/Logo/assets/ButternutFavicon.tsx +33 -0
- package/src/components/atoms/Logo/assets/ButternutPrimary.tsx +294 -0
- package/src/components/atoms/Logo/assets/ButternutTabbedBottom.tsx +294 -0
- package/src/components/atoms/Logo/assets/ButternutTabbedTop.tsx +294 -0
- package/src/components/atoms/Logo/assets/ButternutWordmark.tsx +274 -0
- package/src/components/atoms/Logo/assets/PsiBufetFavicon.tsx +45 -0
- package/src/components/atoms/Logo/assets/PsiBufetPrimary.tsx +218 -0
- package/src/components/atoms/Logo/assets/PsiBufetTabbedBottom.tsx +218 -0
- package/src/components/atoms/Logo/assets/PsiBufetTabbedTop.tsx +218 -0
- package/src/components/atoms/Logo/assets/PsiBufetWordmark.tsx +195 -0
- package/src/components/atoms/Logo/assets/index.ts +11 -0
- package/src/components/atoms/NumberInput/NumberInput.stories.tsx +183 -0
- package/src/components/atoms/NumberInput/NumberInput.test.tsx +261 -0
- package/src/components/atoms/NumberInput/NumberInput.tsx +129 -0
- package/src/components/atoms/NumberInput/NumberInputField.tsx +77 -0
- package/src/components/atoms/NumberInput/index.ts +4 -0
- package/src/components/atoms/Spinner/Spinner.test.tsx +46 -0
- package/src/components/atoms/Spinner/Spinner.tsx +14 -5
- package/src/components/atoms/Switch/Switch.test.tsx +92 -0
- package/src/components/atoms/Switch/Switch.tsx +16 -13
- package/src/components/atoms/Tag/Tag.test.tsx +70 -0
- package/src/components/atoms/TextArea/TextArea.stories.tsx +303 -0
- package/src/components/atoms/TextArea/TextArea.test.tsx +416 -0
- package/src/components/atoms/TextArea/TextArea.tsx +171 -0
- package/src/components/atoms/TextArea/TextAreaField.tsx +304 -0
- package/src/components/atoms/TextArea/TextAreaLabel.tsx +103 -0
- package/src/components/atoms/TextArea/index.ts +6 -0
- package/src/components/atoms/Typography/Typography.test.tsx +94 -0
- package/src/components/atoms/index.ts +3 -0
- package/src/components/molecules/Accordion/Accordion.stories.tsx +177 -0
- package/src/components/molecules/Accordion/Accordion.test.tsx +185 -0
- package/src/components/molecules/Accordion/Accordion.tsx +284 -0
- package/src/components/molecules/Accordion/index.ts +6 -0
- package/src/components/molecules/Animated/Animated.stories.tsx +254 -0
- package/src/components/molecules/Animated/Animated.tsx +283 -0
- package/src/components/molecules/Animated/index.ts +10 -0
- package/src/components/molecules/ButtonDock/ButtonDock.test.tsx +83 -0
- package/src/components/molecules/ButtonGroup/ButtonGroup.stories.tsx +8 -14
- package/src/components/molecules/ButtonGroup/ButtonGroup.test.tsx +73 -0
- package/src/components/molecules/ButtonGroup/ButtonGroup.tsx +25 -3
- package/src/components/molecules/Checkbox/Checkbox.stories.tsx +72 -0
- package/src/components/molecules/Checkbox/Checkbox.test.tsx +117 -0
- package/src/components/molecules/Checkbox/Checkbox.tsx +101 -95
- package/src/components/molecules/CopyField/CopyField.stories.tsx +313 -0
- package/src/components/molecules/CopyField/CopyField.test.tsx +431 -0
- package/src/components/molecules/CopyField/CopyField.tsx +156 -0
- package/src/components/molecules/CopyField/CopyFieldInput.tsx +127 -0
- package/src/components/molecules/CopyField/hooks/index.ts +1 -0
- package/src/components/molecules/CopyField/hooks/useCopyField.ts +25 -0
- package/src/components/molecules/CopyField/index.ts +4 -0
- package/src/components/molecules/DatePicker/DatePicker.stories.tsx +298 -0
- package/src/components/molecules/DatePicker/DatePicker.test.tsx +201 -0
- package/src/components/molecules/DatePicker/DatePicker.tsx +590 -0
- package/src/components/molecules/DatePicker/index.ts +2 -0
- package/src/components/molecules/Drawer/Drawer.stories.tsx +285 -0
- package/src/components/molecules/Drawer/Drawer.test.tsx +180 -0
- package/src/components/molecules/Drawer/Drawer.tsx +187 -0
- package/src/components/molecules/Drawer/DrawerBody.tsx +80 -0
- package/src/components/molecules/Drawer/DrawerClose.tsx +76 -0
- package/src/components/molecules/Drawer/DrawerContent.tsx +339 -0
- package/src/components/molecules/Drawer/DrawerContext.ts +19 -0
- package/src/components/molecules/Drawer/DrawerDescription.tsx +31 -0
- package/src/components/molecules/Drawer/DrawerDragContext.ts +11 -0
- package/src/components/molecules/Drawer/DrawerFooter.tsx +49 -0
- package/src/components/molecules/Drawer/DrawerFooterContext.ts +6 -0
- package/src/components/molecules/Drawer/DrawerGrabber.tsx +62 -0
- package/src/components/molecules/Drawer/DrawerHeader.tsx +244 -0
- package/src/components/molecules/Drawer/DrawerHeaderContext.ts +13 -0
- package/src/components/molecules/Drawer/DrawerOverlay.tsx +53 -0
- package/src/components/molecules/Drawer/DrawerTitle.tsx +32 -0
- package/src/components/molecules/Drawer/index.ts +12 -0
- package/src/components/molecules/FilterTab/FilterTab.stories.tsx +210 -0
- package/src/components/molecules/FilterTab/FilterTab.tsx +310 -0
- package/src/components/molecules/FilterTab/index.ts +2 -0
- package/src/components/molecules/MessageCard/MessageCard.stories.tsx +169 -0
- package/src/components/molecules/MessageCard/MessageCard.tsx +362 -0
- package/src/components/molecules/MessageCard/index.ts +10 -0
- package/src/components/molecules/Notification/Notification.stories.tsx +219 -0
- package/src/components/molecules/Notification/Notification.tsx +426 -0
- package/src/components/molecules/Notification/index.ts +2 -0
- package/src/components/molecules/NumberField/NumberField.stories.tsx +231 -0
- package/src/components/molecules/NumberField/NumberField.tsx +186 -0
- package/src/components/molecules/NumberField/NumberFieldInput.tsx +287 -0
- package/src/components/molecules/NumberField/index.ts +2 -0
- package/src/components/molecules/PasswordField/PasswordField.stories.tsx +362 -0
- package/src/components/molecules/PasswordField/PasswordField.test.tsx +369 -0
- package/src/components/molecules/PasswordField/PasswordField.tsx +194 -0
- package/src/components/molecules/PasswordField/PasswordFieldError.tsx +52 -0
- package/src/components/molecules/PasswordField/PasswordFieldInput.tsx +73 -0
- package/src/components/molecules/PasswordField/PasswordFieldRequirements.tsx +92 -0
- package/src/components/molecules/PasswordField/hooks/index.ts +2 -0
- package/src/components/molecules/PasswordField/hooks/usePasswordField.ts +113 -0
- package/src/components/molecules/PasswordField/index.ts +10 -0
- package/src/components/molecules/PictureSelector/PictureSelector.stories.tsx +243 -0
- package/src/components/molecules/PictureSelector/PictureSelector.tsx +313 -0
- package/src/components/molecules/PictureSelector/index.ts +5 -0
- package/src/components/molecules/Progress/Progress.stories.tsx +145 -0
- package/src/components/molecules/Progress/Progress.tsx +184 -0
- package/src/components/molecules/Progress/index.ts +2 -0
- package/src/components/molecules/Radio/Radio.test.tsx +104 -0
- package/src/components/molecules/Radio/Radio.tsx +1 -2
- package/src/components/molecules/SearchField/SearchField.stories.tsx +242 -0
- package/src/components/molecules/SearchField/SearchField.test.tsx +318 -0
- package/src/components/molecules/SearchField/SearchField.tsx +143 -0
- package/src/components/molecules/SearchField/SearchFieldInput.tsx +63 -0
- package/src/components/molecules/SearchField/hooks/index.ts +1 -0
- package/src/components/molecules/SearchField/hooks/useSearchField.ts +56 -0
- package/src/components/molecules/SearchField/index.ts +4 -0
- package/src/components/molecules/SegmentedControl/SegmentedControl.stories.tsx +31 -8
- package/src/components/molecules/SegmentedControl/SegmentedControl.test.tsx +141 -0
- package/src/components/molecules/SegmentedControl/SegmentedControl.tsx +237 -23
- package/src/components/molecules/SelectField/SelectField.stories.tsx +320 -0
- package/src/components/molecules/SelectField/SelectField.test.tsx +254 -0
- package/src/components/molecules/SelectField/SelectField.tsx +236 -0
- package/src/components/molecules/SelectField/SelectFieldContent.tsx +85 -0
- package/src/components/molecules/SelectField/SelectFieldItem.tsx +133 -0
- package/src/components/molecules/SelectField/SelectFieldTrigger.tsx +170 -0
- package/src/components/molecules/SelectField/SelectFieldValue.tsx +31 -0
- package/src/components/molecules/SelectField/hooks/index.ts +2 -0
- package/src/components/molecules/SelectField/hooks/useSelectField.ts +84 -0
- package/src/components/molecules/SelectField/index.ts +10 -0
- package/src/components/molecules/Slider/Slider.test.tsx +102 -0
- package/src/components/molecules/Slider/Slider.tsx +293 -180
- package/src/components/molecules/Tooltip/Tooltip.stories.tsx +168 -0
- package/src/components/molecules/Tooltip/Tooltip.tsx +326 -0
- package/src/components/molecules/Tooltip/index.ts +2 -0
- package/src/components/molecules/index.ts +15 -0
- package/src/test-utils.tsx +20 -0
- package/tsconfig.json +1 -1
- package/tsup.config.ts +16 -2
- package/vitest.config.ts +114 -0
- package/vitest.setup.ts +16 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
export const Root = React.forwardRef<any, any>(
|
|
4
|
+
(
|
|
5
|
+
{ value, defaultValue, onValueChange, disabled, children, ...rest },
|
|
6
|
+
ref
|
|
7
|
+
) => {
|
|
8
|
+
const [internalValue, setInternalValue] = React.useState(
|
|
9
|
+
value ?? defaultValue ?? null
|
|
10
|
+
)
|
|
11
|
+
const currentValue = value ?? internalValue
|
|
12
|
+
|
|
13
|
+
const handleChange = (newValue: any) => {
|
|
14
|
+
if (!disabled) {
|
|
15
|
+
setInternalValue(newValue)
|
|
16
|
+
onValueChange?.(newValue)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div ref={ref} data-disabled={disabled} {...rest}>
|
|
22
|
+
{typeof children === "function"
|
|
23
|
+
? children({ value: currentValue, onChange: handleChange })
|
|
24
|
+
: children}
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
Root.displayName = "SelectPrimitive.Root"
|
|
30
|
+
|
|
31
|
+
export const Trigger = React.forwardRef<any, any>(
|
|
32
|
+
({ asChild, children, ...rest }, ref) => {
|
|
33
|
+
if (asChild && React.isValidElement(children)) {
|
|
34
|
+
return React.cloneElement(children, { ref, ...rest } as any)
|
|
35
|
+
}
|
|
36
|
+
return (
|
|
37
|
+
<button ref={ref} role="button" {...rest}>
|
|
38
|
+
{children}
|
|
39
|
+
</button>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
Trigger.displayName = "SelectPrimitive.Trigger"
|
|
44
|
+
|
|
45
|
+
export const Value = React.forwardRef<any, any>(
|
|
46
|
+
({ placeholder, asChild, children, ...rest }, ref) => {
|
|
47
|
+
if (asChild && React.isValidElement(children)) {
|
|
48
|
+
return React.cloneElement(children, { ref, ...rest } as any)
|
|
49
|
+
}
|
|
50
|
+
return (
|
|
51
|
+
<span ref={ref} {...rest}>
|
|
52
|
+
{children || placeholder}
|
|
53
|
+
</span>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
Value.displayName = "SelectPrimitive.Value"
|
|
58
|
+
|
|
59
|
+
export const Portal = ({ children }: any) => <>{children}</>
|
|
60
|
+
Portal.displayName = "SelectPrimitive.Portal"
|
|
61
|
+
|
|
62
|
+
export const Viewport = React.forwardRef<any, any>(
|
|
63
|
+
({ children, ...rest }, ref) => (
|
|
64
|
+
<div ref={ref} {...rest}>
|
|
65
|
+
{children}
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
Viewport.displayName = "SelectPrimitive.Viewport"
|
|
70
|
+
|
|
71
|
+
export const Overlay = React.forwardRef<any, any>(
|
|
72
|
+
({ asChild, children, ...rest }, ref) => {
|
|
73
|
+
if (asChild && React.isValidElement(children)) {
|
|
74
|
+
return React.cloneElement(children, { ref, ...rest } as any)
|
|
75
|
+
}
|
|
76
|
+
return (
|
|
77
|
+
<div ref={ref} {...rest}>
|
|
78
|
+
{children}
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
Overlay.displayName = "SelectPrimitive.Overlay"
|
|
84
|
+
|
|
85
|
+
export const Content = React.forwardRef<any, any>(
|
|
86
|
+
({ asChild, children, ...rest }, ref) => {
|
|
87
|
+
if (asChild && React.isValidElement(children)) {
|
|
88
|
+
return React.cloneElement(children, { ref, ...rest } as any)
|
|
89
|
+
}
|
|
90
|
+
return (
|
|
91
|
+
<div ref={ref} role="listbox" {...rest}>
|
|
92
|
+
{children}
|
|
93
|
+
</div>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
Content.displayName = "SelectPrimitive.Content"
|
|
98
|
+
|
|
99
|
+
export const Item = React.forwardRef<any, any>(
|
|
100
|
+
({ value: _value, label, disabled, asChild, children, ...rest }, ref) => {
|
|
101
|
+
if (asChild && React.isValidElement(children)) {
|
|
102
|
+
return React.cloneElement(children, {
|
|
103
|
+
ref,
|
|
104
|
+
role: "option",
|
|
105
|
+
"aria-disabled": disabled,
|
|
106
|
+
...rest
|
|
107
|
+
} as any)
|
|
108
|
+
}
|
|
109
|
+
return (
|
|
110
|
+
<div ref={ref} role="option" aria-disabled={disabled} {...rest}>
|
|
111
|
+
{children || label}
|
|
112
|
+
</div>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
Item.displayName = "SelectPrimitive.Item"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
export const Root = React.forwardRef<any, any>(
|
|
4
|
+
({ children, value, min, max, disabled, ..._rest }, ref) => (
|
|
5
|
+
<div
|
|
6
|
+
ref={ref}
|
|
7
|
+
role="slider"
|
|
8
|
+
aria-valuenow={value}
|
|
9
|
+
aria-valuemin={min}
|
|
10
|
+
aria-valuemax={max}
|
|
11
|
+
aria-disabled={disabled}
|
|
12
|
+
>
|
|
13
|
+
{children}
|
|
14
|
+
</div>
|
|
15
|
+
)
|
|
16
|
+
)
|
|
17
|
+
Root.displayName = "SliderPrimitive.Root"
|
|
18
|
+
|
|
19
|
+
export const Track = React.forwardRef<any, any>(
|
|
20
|
+
({ children, ..._rest }, ref) => (
|
|
21
|
+
<div ref={ref} data-testid="slider-track">
|
|
22
|
+
{children}
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
Track.displayName = "SliderPrimitive.Track"
|
|
27
|
+
|
|
28
|
+
export const Range = React.forwardRef<any, any>(({ style, ..._rest }, ref) => (
|
|
29
|
+
<div ref={ref} data-testid="slider-range" style={style} />
|
|
30
|
+
))
|
|
31
|
+
Range.displayName = "SliderPrimitive.Range"
|
|
32
|
+
|
|
33
|
+
export const Thumb = React.forwardRef<any, any>(
|
|
34
|
+
({ children, style, ..._rest }, ref) => (
|
|
35
|
+
<div ref={ref} data-testid="slider-thumb" style={style}>
|
|
36
|
+
{children}
|
|
37
|
+
</div>
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
Thumb.displayName = "SliderPrimitive.Thumb"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
export const Slot = React.forwardRef<any, any>(
|
|
4
|
+
({ children, ...slotProps }, ref) => {
|
|
5
|
+
if (React.isValidElement(children)) {
|
|
6
|
+
const childProps = children.props as Record<string, any>
|
|
7
|
+
const mergedProps = { ...slotProps, ...childProps }
|
|
8
|
+
if (slotProps.onPress && childProps.onPress) {
|
|
9
|
+
mergedProps.onPress = (...args: any[]) => {
|
|
10
|
+
slotProps.onPress(...args)
|
|
11
|
+
childProps.onPress(...args)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// Map onPress to onClick for DOM elements so fireEvent.click works in tests
|
|
15
|
+
if (slotProps.onPress && !mergedProps.onClick) {
|
|
16
|
+
mergedProps.onClick = (...args: any[]) => mergedProps.onPress?.(...args)
|
|
17
|
+
}
|
|
18
|
+
return React.cloneElement(children as React.ReactElement<any>, {
|
|
19
|
+
...mergedProps,
|
|
20
|
+
ref
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
return (
|
|
24
|
+
<div ref={ref} {...slotProps}>
|
|
25
|
+
{children}
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
Slot.displayName = "Slot"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
export const Root = React.forwardRef<any, any>(
|
|
4
|
+
({ checked, onCheckedChange, disabled, children, ..._rest }, ref) => (
|
|
5
|
+
<button
|
|
6
|
+
ref={ref}
|
|
7
|
+
role="switch"
|
|
8
|
+
aria-checked={checked}
|
|
9
|
+
disabled={disabled}
|
|
10
|
+
onClick={() => !disabled && onCheckedChange?.(!checked)}
|
|
11
|
+
>
|
|
12
|
+
{children}
|
|
13
|
+
</button>
|
|
14
|
+
)
|
|
15
|
+
)
|
|
16
|
+
Root.displayName = "SwitchPrimitive.Root"
|
|
17
|
+
|
|
18
|
+
export const Thumb = ({ children, asChild, ..._rest }: any) => {
|
|
19
|
+
if (asChild && React.isValidElement(children)) {
|
|
20
|
+
return children
|
|
21
|
+
}
|
|
22
|
+
return <div>{children}</div>
|
|
23
|
+
}
|
|
24
|
+
Thumb.displayName = "SwitchPrimitive.Thumb"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
export const Root = React.forwardRef<any, any>(
|
|
4
|
+
({ children, pressed, onPressedChange, disabled, ..._rest }, ref) => (
|
|
5
|
+
<button
|
|
6
|
+
ref={ref}
|
|
7
|
+
role="switch"
|
|
8
|
+
aria-pressed={pressed}
|
|
9
|
+
disabled={disabled}
|
|
10
|
+
onClick={() => !disabled && onPressedChange?.(!pressed)}
|
|
11
|
+
>
|
|
12
|
+
{children}
|
|
13
|
+
</button>
|
|
14
|
+
)
|
|
15
|
+
)
|
|
16
|
+
Root.displayName = "TogglePrimitive.Root"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React from "react"
|
|
2
2
|
import { View, StyleSheet } from "react-native"
|
|
3
3
|
import { Avatar } from "./Avatar"
|
|
4
|
-
import type { AvatarProps } from "./Avatar"
|
|
5
4
|
|
|
6
5
|
const DOG_PHOTO_URL = "https://placedog.net/200/200"
|
|
7
6
|
|
|
@@ -20,12 +19,13 @@ export default {
|
|
|
20
19
|
size: {
|
|
21
20
|
control: { type: "select" },
|
|
22
21
|
options: ["sm", "md", "lg"],
|
|
23
|
-
description: "
|
|
22
|
+
description: "Avatar size: sm (32px), md (40px), lg (48px)"
|
|
24
23
|
},
|
|
25
24
|
border: {
|
|
26
25
|
control: { type: "select" },
|
|
27
26
|
options: ["none", "sm", "md"],
|
|
28
|
-
description:
|
|
27
|
+
description:
|
|
28
|
+
"Border width. Small avatars: none/sm (1px). Medium/Large: none/md (2px)"
|
|
29
29
|
},
|
|
30
30
|
fallbackType: {
|
|
31
31
|
control: { type: "select" },
|
|
@@ -39,13 +39,9 @@ export default {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export const Default = (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
alt: "User Photo",
|
|
46
|
-
size: "md",
|
|
47
|
-
border: "none"
|
|
48
|
-
}
|
|
42
|
+
export const Default = () => (
|
|
43
|
+
<Avatar src={DOG_PHOTO_URL} alt="User Photo" size="md" />
|
|
44
|
+
)
|
|
49
45
|
|
|
50
46
|
export const Sizes = () => (
|
|
51
47
|
<View style={styles.row}>
|
|
@@ -55,71 +51,83 @@ export const Sizes = () => (
|
|
|
55
51
|
</View>
|
|
56
52
|
)
|
|
57
53
|
|
|
58
|
-
export const
|
|
59
|
-
<View style={styles.
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
export const Image = () => (
|
|
55
|
+
<View style={styles.column}>
|
|
56
|
+
<View style={styles.row}>
|
|
57
|
+
<Avatar src={DOG_PHOTO_URL} alt="Small" size="sm" border="none" />
|
|
58
|
+
<Avatar src={DOG_PHOTO_URL} alt="Small" size="sm" border="sm" />
|
|
59
|
+
</View>
|
|
60
|
+
<View style={styles.row}>
|
|
61
|
+
<Avatar src={DOG_PHOTO_URL} alt="Medium" size="md" border="none" />
|
|
62
|
+
<Avatar src={DOG_PHOTO_URL} alt="Medium" size="md" border="md" />
|
|
63
|
+
</View>
|
|
64
|
+
<View style={styles.row}>
|
|
65
|
+
<Avatar src={DOG_PHOTO_URL} alt="Large" size="lg" border="none" />
|
|
66
|
+
<Avatar src={DOG_PHOTO_URL} alt="Large" size="lg" border="md" />
|
|
67
|
+
</View>
|
|
63
68
|
</View>
|
|
64
69
|
)
|
|
65
70
|
|
|
66
|
-
export const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
<
|
|
77
|
-
|
|
71
|
+
export const FallbackIcon = () => (
|
|
72
|
+
<View style={styles.column}>
|
|
73
|
+
<View style={styles.row}>
|
|
74
|
+
<Avatar alt="Small" fallbackType="image" size="sm" border="none" />
|
|
75
|
+
<Avatar alt="Small" fallbackType="image" size="sm" border="sm" />
|
|
76
|
+
</View>
|
|
77
|
+
<View style={styles.row}>
|
|
78
|
+
<Avatar alt="Medium" fallbackType="image" size="md" border="none" />
|
|
79
|
+
<Avatar alt="Medium" fallbackType="image" size="md" border="md" />
|
|
80
|
+
</View>
|
|
81
|
+
<View style={styles.row}>
|
|
82
|
+
<Avatar alt="Large" fallbackType="image" size="lg" border="none" />
|
|
83
|
+
<Avatar alt="Large" fallbackType="image" size="lg" border="md" />
|
|
84
|
+
</View>
|
|
78
85
|
</View>
|
|
79
86
|
)
|
|
80
87
|
|
|
81
|
-
export const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
export const FallbackString = () => (
|
|
89
|
+
<View style={styles.column}>
|
|
90
|
+
<View style={styles.row}>
|
|
91
|
+
<Avatar alt="Small" fallbackString="JD" size="sm" border="none" />
|
|
92
|
+
<Avatar alt="Small" fallbackString="JD" size="sm" border="sm" />
|
|
93
|
+
</View>
|
|
94
|
+
<View style={styles.row}>
|
|
95
|
+
<Avatar alt="Medium" fallbackString="JD" size="md" border="none" />
|
|
96
|
+
<Avatar alt="Medium" fallbackString="JD" size="md" border="md" />
|
|
97
|
+
</View>
|
|
98
|
+
<View style={styles.row}>
|
|
99
|
+
<Avatar alt="Large" fallbackString="JD" size="lg" border="none" />
|
|
100
|
+
<Avatar alt="Large" fallbackString="JD" size="lg" border="md" />
|
|
101
|
+
</View>
|
|
102
|
+
</View>
|
|
103
|
+
)
|
|
87
104
|
|
|
88
|
-
export const
|
|
105
|
+
export const BrokenImageFallback = () => (
|
|
89
106
|
<View style={styles.row}>
|
|
90
107
|
<Avatar
|
|
91
108
|
src="https://broken-link.com/photo.jpg"
|
|
92
109
|
alt="Broken with string fallback"
|
|
93
110
|
fallbackString="JD"
|
|
94
111
|
fallbackType="string"
|
|
112
|
+
size="md"
|
|
95
113
|
/>
|
|
96
114
|
<Avatar
|
|
97
115
|
src="https://broken-link.com/photo.jpg"
|
|
98
116
|
alt="Broken with image fallback"
|
|
99
117
|
fallbackType="image"
|
|
118
|
+
size="md"
|
|
100
119
|
/>
|
|
101
120
|
</View>
|
|
102
121
|
)
|
|
103
122
|
|
|
104
|
-
export const BordersWithFallback = () => (
|
|
105
|
-
<View style={styles.row}>
|
|
106
|
-
<Avatar alt="No border" fallbackString="AB" border="none" />
|
|
107
|
-
<Avatar alt="Small border" fallbackString="CD" border="sm" />
|
|
108
|
-
<Avatar alt="Medium border" fallbackString="EF" border="md" />
|
|
109
|
-
</View>
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
export const LongInitials = (args: AvatarProps) => <Avatar {...args} />
|
|
113
|
-
LongInitials.args = {
|
|
114
|
-
alt: "Long initials",
|
|
115
|
-
fallbackString: "ABC",
|
|
116
|
-
size: "lg"
|
|
117
|
-
}
|
|
118
|
-
|
|
119
123
|
const styles = StyleSheet.create({
|
|
120
124
|
row: {
|
|
121
125
|
flexDirection: "row",
|
|
122
126
|
gap: 16,
|
|
123
127
|
alignItems: "center"
|
|
128
|
+
},
|
|
129
|
+
column: {
|
|
130
|
+
flexDirection: "column",
|
|
131
|
+
gap: 12
|
|
124
132
|
}
|
|
125
133
|
})
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { screen } from "@testing-library/react"
|
|
3
|
+
import { describe, it, expect } from "vitest"
|
|
4
|
+
import { renderWithTheme } from "../../../test-utils"
|
|
5
|
+
import { Avatar } from "./Avatar"
|
|
6
|
+
|
|
7
|
+
describe("Avatar", () => {
|
|
8
|
+
describe("when rendering with image", () => {
|
|
9
|
+
it("renders image when src is provided", () => {
|
|
10
|
+
const { container } = renderWithTheme(
|
|
11
|
+
<Avatar src="https://example.com/photo.jpg" alt="User Photo" />
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
const img = container.querySelector("img")
|
|
15
|
+
expect(img).toBeInTheDocument()
|
|
16
|
+
expect(img).toHaveAttribute("src", "https://example.com/photo.jpg")
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it("renders with alt text", () => {
|
|
20
|
+
renderWithTheme(
|
|
21
|
+
<Avatar
|
|
22
|
+
src="https://example.com/photo.jpg"
|
|
23
|
+
alt="John Doe Profile Picture"
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
expect(
|
|
28
|
+
screen.getByRole("img", { name: "John Doe Profile Picture" })
|
|
29
|
+
).toBeInTheDocument()
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe("when rendering fallback", () => {
|
|
34
|
+
it("renders fallback icon when no src is provided", () => {
|
|
35
|
+
renderWithTheme(<Avatar alt="User avatar" fallbackType="image" />)
|
|
36
|
+
|
|
37
|
+
expect(screen.getByLabelText("Profile image")).toBeInTheDocument()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it("renders fallback string when fallbackString is provided", () => {
|
|
41
|
+
renderWithTheme(<Avatar alt="User" fallbackString="JD" />)
|
|
42
|
+
|
|
43
|
+
expect(screen.getByText("JD")).toBeInTheDocument()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it("automatically uses string fallback when fallbackString is provided without fallbackType", () => {
|
|
47
|
+
renderWithTheme(<Avatar alt="User" fallbackString="AB" />)
|
|
48
|
+
|
|
49
|
+
expect(screen.getByText("AB")).toBeInTheDocument()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it("renders icon fallback when no fallbackString is provided", () => {
|
|
53
|
+
renderWithTheme(<Avatar alt="User avatar" />)
|
|
54
|
+
|
|
55
|
+
expect(screen.getByLabelText("Profile image")).toBeInTheDocument()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it("renders string fallback even when src is provided but fails to load", () => {
|
|
59
|
+
renderWithTheme(
|
|
60
|
+
<Avatar
|
|
61
|
+
src="https://broken-link.com/photo.jpg"
|
|
62
|
+
alt="User"
|
|
63
|
+
fallbackString="JD"
|
|
64
|
+
fallbackType="string"
|
|
65
|
+
/>
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
expect(screen.getByText("JD")).toBeInTheDocument()
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
describe("size variants", () => {
|
|
73
|
+
it("renders small size", () => {
|
|
74
|
+
const { container } = renderWithTheme(
|
|
75
|
+
<Avatar src="https://example.com/photo.jpg" alt="User" size="sm" />
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it("renders medium size (default)", () => {
|
|
82
|
+
const { container } = renderWithTheme(
|
|
83
|
+
<Avatar src="https://example.com/photo.jpg" alt="User" />
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it("renders large size", () => {
|
|
90
|
+
const { container } = renderWithTheme(
|
|
91
|
+
<Avatar src="https://example.com/photo.jpg" alt="User" size="lg" />
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
describe("border variants", () => {
|
|
99
|
+
it("renders without border by default", () => {
|
|
100
|
+
const { container } = renderWithTheme(
|
|
101
|
+
<Avatar src="https://example.com/photo.jpg" alt="User" />
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it("renders small avatar with small border", () => {
|
|
108
|
+
const { container } = renderWithTheme(
|
|
109
|
+
<Avatar
|
|
110
|
+
src="https://example.com/photo.jpg"
|
|
111
|
+
alt="User"
|
|
112
|
+
size="sm"
|
|
113
|
+
border="sm"
|
|
114
|
+
/>
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it("renders medium avatar with medium border", () => {
|
|
121
|
+
const { container } = renderWithTheme(
|
|
122
|
+
<Avatar
|
|
123
|
+
src="https://example.com/photo.jpg"
|
|
124
|
+
alt="User"
|
|
125
|
+
size="md"
|
|
126
|
+
border="md"
|
|
127
|
+
/>
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it("renders large avatar with medium border", () => {
|
|
134
|
+
const { container } = renderWithTheme(
|
|
135
|
+
<Avatar
|
|
136
|
+
src="https://example.com/photo.jpg"
|
|
137
|
+
alt="User"
|
|
138
|
+
size="lg"
|
|
139
|
+
border="md"
|
|
140
|
+
/>
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
describe("fallback type combinations", () => {
|
|
148
|
+
it("renders image fallback for small size", () => {
|
|
149
|
+
renderWithTheme(<Avatar alt="User" fallbackType="image" size="sm" />)
|
|
150
|
+
|
|
151
|
+
expect(screen.getByLabelText("Profile image")).toBeInTheDocument()
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it("renders image fallback for medium size", () => {
|
|
155
|
+
renderWithTheme(<Avatar alt="User" fallbackType="image" size="md" />)
|
|
156
|
+
|
|
157
|
+
expect(screen.getByLabelText("Profile image")).toBeInTheDocument()
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it("renders image fallback for large size", () => {
|
|
161
|
+
renderWithTheme(<Avatar alt="User" fallbackType="image" size="lg" />)
|
|
162
|
+
|
|
163
|
+
expect(screen.getByLabelText("Profile image")).toBeInTheDocument()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it("renders string fallback for small size", () => {
|
|
167
|
+
renderWithTheme(<Avatar alt="User" fallbackString="SM" size="sm" />)
|
|
168
|
+
|
|
169
|
+
expect(screen.getByText("SM")).toBeInTheDocument()
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it("renders string fallback for medium size", () => {
|
|
173
|
+
renderWithTheme(<Avatar alt="User" fallbackString="MD" size="md" />)
|
|
174
|
+
|
|
175
|
+
expect(screen.getByText("MD")).toBeInTheDocument()
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it("renders string fallback for large size", () => {
|
|
179
|
+
renderWithTheme(<Avatar alt="User" fallbackString="LG" size="lg" />)
|
|
180
|
+
|
|
181
|
+
expect(screen.getByText("LG")).toBeInTheDocument()
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
describe("accessibility", () => {
|
|
186
|
+
it("has accessible image with alt text", () => {
|
|
187
|
+
renderWithTheme(
|
|
188
|
+
<Avatar src="https://example.com/photo.jpg" alt="User Avatar" />
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
expect(
|
|
192
|
+
screen.getByRole("img", { name: "User Avatar" })
|
|
193
|
+
).toBeInTheDocument()
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it("has accessible fallback icon with aria-label", () => {
|
|
197
|
+
renderWithTheme(<Avatar alt="User" fallbackType="image" />)
|
|
198
|
+
|
|
199
|
+
expect(screen.getByLabelText("Profile image")).toBeInTheDocument()
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it("renders fallback text accessibly", () => {
|
|
203
|
+
renderWithTheme(<Avatar alt="User" fallbackString="JD" />)
|
|
204
|
+
|
|
205
|
+
expect(screen.getByText("JD")).toBeInTheDocument()
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
describe("type safety", () => {
|
|
210
|
+
it("allows small avatar with none border", () => {
|
|
211
|
+
const { container } = renderWithTheme(
|
|
212
|
+
<Avatar alt="User" size="sm" border="none" fallbackString="SM" />
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it("allows small avatar with sm border", () => {
|
|
219
|
+
const { container } = renderWithTheme(
|
|
220
|
+
<Avatar alt="User" size="sm" border="sm" fallbackString="SM" />
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it("allows medium avatar with none border", () => {
|
|
227
|
+
const { container } = renderWithTheme(
|
|
228
|
+
<Avatar alt="User" size="md" border="none" fallbackString="MD" />
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it("allows medium avatar with md border", () => {
|
|
235
|
+
const { container } = renderWithTheme(
|
|
236
|
+
<Avatar alt="User" size="md" border="md" fallbackString="MD" />
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
it("allows large avatar with none border", () => {
|
|
243
|
+
const { container } = renderWithTheme(
|
|
244
|
+
<Avatar alt="User" size="lg" border="none" fallbackString="LG" />
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it("allows large avatar with md border", () => {
|
|
251
|
+
const { container } = renderWithTheme(
|
|
252
|
+
<Avatar alt="User" size="lg" border="md" fallbackString="LG" />
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
describe("ref forwarding", () => {
|
|
260
|
+
it("forwards ref to the root element", () => {
|
|
261
|
+
const ref = React.createRef<any>()
|
|
262
|
+
|
|
263
|
+
renderWithTheme(<Avatar ref={ref} alt="User" />)
|
|
264
|
+
|
|
265
|
+
expect(ref.current).toBeTruthy()
|
|
266
|
+
expect(ref.current).toBeInTheDocument()
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
})
|