@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,362 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { View, ViewProps, Image, ImageSourcePropType } from "react-native"
|
|
3
|
+
import styled from "@emotion/native"
|
|
4
|
+
import { useTheme } from "@emotion/react"
|
|
5
|
+
import { Icon, type PawprintIcon } from "../../atoms/Icon"
|
|
6
|
+
import { Illustration } from "../../atoms/Illustration"
|
|
7
|
+
import { Typography } from "../../atoms/Typography"
|
|
8
|
+
import { Link } from "../../atoms/Link"
|
|
9
|
+
|
|
10
|
+
// ─── Shared ───────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
type PawprintIllustration = React.ComponentType<{
|
|
13
|
+
width?: number
|
|
14
|
+
height?: number
|
|
15
|
+
}>
|
|
16
|
+
|
|
17
|
+
const parseTokenValue = (value: string): number => parseFloat(value)
|
|
18
|
+
|
|
19
|
+
// ─── Insight variant ──────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
type InsightVariant = "standalone" | "supporting"
|
|
22
|
+
|
|
23
|
+
type MessageCardInsightOwnProps = {
|
|
24
|
+
variant?: InsightVariant
|
|
25
|
+
icon?: PawprintIcon
|
|
26
|
+
illustration?: PawprintIllustration
|
|
27
|
+
title?: string
|
|
28
|
+
children: React.ReactNode
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type MessageCardInsightProps = MessageCardInsightOwnProps &
|
|
32
|
+
Omit<ViewProps, keyof MessageCardInsightOwnProps>
|
|
33
|
+
|
|
34
|
+
const StyledInsightRoot = styled(View)<{
|
|
35
|
+
rootGap: number
|
|
36
|
+
rootPaddingH: number
|
|
37
|
+
rootPaddingTop: number
|
|
38
|
+
rootPaddingBottom: number
|
|
39
|
+
rootBorderTopLeftRadius: number
|
|
40
|
+
rootBorderTopRightRadius: number
|
|
41
|
+
rootBorderBottomLeftRadius: number
|
|
42
|
+
rootBorderBottomRightRadius: number
|
|
43
|
+
rootBgColor: string
|
|
44
|
+
rootMinWidth: number
|
|
45
|
+
}>(
|
|
46
|
+
({
|
|
47
|
+
rootGap,
|
|
48
|
+
rootPaddingH,
|
|
49
|
+
rootPaddingTop,
|
|
50
|
+
rootPaddingBottom,
|
|
51
|
+
rootBorderTopLeftRadius,
|
|
52
|
+
rootBorderTopRightRadius,
|
|
53
|
+
rootBorderBottomLeftRadius,
|
|
54
|
+
rootBorderBottomRightRadius,
|
|
55
|
+
rootBgColor,
|
|
56
|
+
rootMinWidth
|
|
57
|
+
}) => ({
|
|
58
|
+
flexDirection: "row",
|
|
59
|
+
alignItems: "center",
|
|
60
|
+
gap: rootGap,
|
|
61
|
+
paddingHorizontal: rootPaddingH,
|
|
62
|
+
paddingTop: rootPaddingTop,
|
|
63
|
+
paddingBottom: rootPaddingBottom,
|
|
64
|
+
borderTopLeftRadius: rootBorderTopLeftRadius,
|
|
65
|
+
borderTopRightRadius: rootBorderTopRightRadius,
|
|
66
|
+
borderBottomLeftRadius: rootBorderBottomLeftRadius,
|
|
67
|
+
borderBottomRightRadius: rootBorderBottomRightRadius,
|
|
68
|
+
backgroundColor: rootBgColor,
|
|
69
|
+
minWidth: rootMinWidth
|
|
70
|
+
})
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
const StyledInsightContent = styled(View)<{ contentGap: number }>(
|
|
74
|
+
({ contentGap }) => ({
|
|
75
|
+
flex: 1,
|
|
76
|
+
flexDirection: "column",
|
|
77
|
+
minWidth: 0,
|
|
78
|
+
gap: contentGap
|
|
79
|
+
})
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Yellow highlight card for bite-sized USPs and trust signals.
|
|
84
|
+
*
|
|
85
|
+
* @param {"standalone" | "supporting"} [variant="standalone"] - `supporting`
|
|
86
|
+
* renders a flat top edge when attached to a related element above.
|
|
87
|
+
* @param {PawprintIcon} [icon] - Optional core icon. Ignored when
|
|
88
|
+
* `illustration` is provided.
|
|
89
|
+
* @param {PawprintIllustration} [illustration] - Optional illustration; takes
|
|
90
|
+
* precedence over `icon`.
|
|
91
|
+
* @param {string} [title] - Optional headline. Renders above the body copy
|
|
92
|
+
* in both `standalone` and `supporting` variants.
|
|
93
|
+
* @param {React.ReactNode} children - Body copy.
|
|
94
|
+
*/
|
|
95
|
+
const MessageCardInsight = React.forwardRef<View, MessageCardInsightProps>(
|
|
96
|
+
(
|
|
97
|
+
{ variant = "standalone", icon, illustration, title, children, ...rest },
|
|
98
|
+
ref
|
|
99
|
+
) => {
|
|
100
|
+
const theme = useTheme()
|
|
101
|
+
const { insightCard } = theme.tokens.components
|
|
102
|
+
const semanticSpacing = theme.tokens.semantics.dimensions.spacing
|
|
103
|
+
|
|
104
|
+
const isSupporting = variant === "supporting"
|
|
105
|
+
const supporting = insightCard.spacing.supporting
|
|
106
|
+
const standalone = insightCard.spacing.standalone
|
|
107
|
+
|
|
108
|
+
const radiusStandalone = parseTokenValue(
|
|
109
|
+
insightCard.borderRadius.standalone.default
|
|
110
|
+
)
|
|
111
|
+
const radiusSupporting = parseTokenValue(
|
|
112
|
+
insightCard.borderRadius.supporting.bottom
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<StyledInsightRoot
|
|
117
|
+
ref={ref}
|
|
118
|
+
rootGap={parseTokenValue(
|
|
119
|
+
isSupporting ? supporting.gap : standalone.gap
|
|
120
|
+
)}
|
|
121
|
+
rootPaddingH={parseTokenValue(
|
|
122
|
+
isSupporting ? semanticSpacing.md : standalone.horizontalPadding
|
|
123
|
+
)}
|
|
124
|
+
rootPaddingTop={parseTokenValue(
|
|
125
|
+
isSupporting ? supporting.topPadding : standalone.verticalPadding
|
|
126
|
+
)}
|
|
127
|
+
rootPaddingBottom={parseTokenValue(
|
|
128
|
+
isSupporting ? supporting.bottomPadding : standalone.verticalPadding
|
|
129
|
+
)}
|
|
130
|
+
rootBorderTopLeftRadius={isSupporting ? 0 : radiusStandalone}
|
|
131
|
+
rootBorderTopRightRadius={isSupporting ? 0 : radiusStandalone}
|
|
132
|
+
rootBorderBottomLeftRadius={
|
|
133
|
+
isSupporting ? radiusSupporting : radiusStandalone
|
|
134
|
+
}
|
|
135
|
+
rootBorderBottomRightRadius={
|
|
136
|
+
isSupporting ? radiusSupporting : radiusStandalone
|
|
137
|
+
}
|
|
138
|
+
rootBgColor={insightCard.colour.background.default}
|
|
139
|
+
rootMinWidth={parseTokenValue(insightCard.size.minWidth)}
|
|
140
|
+
accessible
|
|
141
|
+
accessibilityRole="summary"
|
|
142
|
+
{...rest}
|
|
143
|
+
>
|
|
144
|
+
{illustration ? (
|
|
145
|
+
<Illustration illustration={illustration} size="sm" />
|
|
146
|
+
) : icon ? (
|
|
147
|
+
<Icon icon={icon} size="xl" colour="primary" />
|
|
148
|
+
) : null}
|
|
149
|
+
<StyledInsightContent
|
|
150
|
+
contentGap={parseTokenValue(standalone.content.gap)}
|
|
151
|
+
>
|
|
152
|
+
{title && (
|
|
153
|
+
<Typography
|
|
154
|
+
token={insightCard.typography.title}
|
|
155
|
+
color={insightCard.colour.text.title}
|
|
156
|
+
>
|
|
157
|
+
{title}
|
|
158
|
+
</Typography>
|
|
159
|
+
)}
|
|
160
|
+
<Typography
|
|
161
|
+
token={insightCard.typography.subText}
|
|
162
|
+
color={insightCard.colour.text.subText}
|
|
163
|
+
>
|
|
164
|
+
{children}
|
|
165
|
+
</Typography>
|
|
166
|
+
</StyledInsightContent>
|
|
167
|
+
</StyledInsightRoot>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
MessageCardInsight.displayName = "MessageCard.Insight"
|
|
173
|
+
|
|
174
|
+
// ─── Banner variant ───────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
type BannerColourScheme = "primary" | "secondary"
|
|
177
|
+
|
|
178
|
+
export type MessageCardBannerMedia =
|
|
179
|
+
| { type: "image"; source: ImageSourcePropType; alt?: string }
|
|
180
|
+
| { type: "illustration"; illustration: PawprintIllustration }
|
|
181
|
+
|
|
182
|
+
type MessageCardBannerOwnProps = {
|
|
183
|
+
colourScheme?: BannerColourScheme
|
|
184
|
+
media?: MessageCardBannerMedia
|
|
185
|
+
title?: string
|
|
186
|
+
children: React.ReactNode
|
|
187
|
+
linkLabel?: string
|
|
188
|
+
linkHref?: string
|
|
189
|
+
onLinkPress?: () => void
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export type MessageCardBannerProps = MessageCardBannerOwnProps &
|
|
193
|
+
Omit<ViewProps, keyof MessageCardBannerOwnProps>
|
|
194
|
+
|
|
195
|
+
const StyledBannerRoot = styled(View)<{
|
|
196
|
+
rootGap: number
|
|
197
|
+
rootPadding: number
|
|
198
|
+
rootBorderRadius: number
|
|
199
|
+
rootBgColor: string
|
|
200
|
+
}>(({ rootGap, rootPadding, rootBorderRadius, rootBgColor }) => ({
|
|
201
|
+
flexDirection: "row",
|
|
202
|
+
alignItems: "center",
|
|
203
|
+
gap: rootGap,
|
|
204
|
+
padding: rootPadding,
|
|
205
|
+
borderRadius: rootBorderRadius,
|
|
206
|
+
backgroundColor: rootBgColor
|
|
207
|
+
}))
|
|
208
|
+
|
|
209
|
+
const StyledBannerImage = styled(View)<{
|
|
210
|
+
imageRadius: number
|
|
211
|
+
imageBgColor: string
|
|
212
|
+
}>(({ imageRadius, imageBgColor }) => ({
|
|
213
|
+
flexShrink: 0,
|
|
214
|
+
width: 108,
|
|
215
|
+
height: 108,
|
|
216
|
+
borderRadius: imageRadius,
|
|
217
|
+
overflow: "hidden",
|
|
218
|
+
backgroundColor: imageBgColor
|
|
219
|
+
}))
|
|
220
|
+
|
|
221
|
+
const StyledBannerIllustrationSlot = styled(View)({
|
|
222
|
+
flexShrink: 0,
|
|
223
|
+
width: 108,
|
|
224
|
+
height: 108,
|
|
225
|
+
alignItems: "center",
|
|
226
|
+
justifyContent: "center"
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
const StyledBannerContents = styled(View)<{ contentsGap: number }>(
|
|
230
|
+
({ contentsGap }) => ({
|
|
231
|
+
flex: 1,
|
|
232
|
+
flexDirection: "column",
|
|
233
|
+
alignItems: "flex-start",
|
|
234
|
+
justifyContent: "center",
|
|
235
|
+
gap: contentsGap,
|
|
236
|
+
minWidth: 0
|
|
237
|
+
})
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
const StyledBannerCopy = styled(View)<{ copyGap: number }>(({ copyGap }) => ({
|
|
241
|
+
flexDirection: "column",
|
|
242
|
+
alignItems: "flex-start",
|
|
243
|
+
gap: copyGap,
|
|
244
|
+
width: "100%"
|
|
245
|
+
}))
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Horizontal card with an image or illustration, title, body copy and an
|
|
249
|
+
* optional link CTA.
|
|
250
|
+
*
|
|
251
|
+
* @param {"primary" | "secondary"} [colourScheme="primary"] - Background
|
|
252
|
+
* scheme. `primary` is saturated yellow; `secondary` is a lighter cream.
|
|
253
|
+
* @param {MessageCardBannerMedia} [media] - Tagged slot accepting either
|
|
254
|
+
* `{ type: "image", source, alt }` or `{ type: "illustration", illustration }`.
|
|
255
|
+
* @param {string} [title] - Optional headline.
|
|
256
|
+
* @param {React.ReactNode} children - Body copy.
|
|
257
|
+
* @param {string} [linkLabel] - CTA label. When provided with `linkHref` or
|
|
258
|
+
* `onLinkPress`, renders a link row.
|
|
259
|
+
* @param {string} [linkHref] - URL opened via `Linking` when pressed.
|
|
260
|
+
* @param {() => void} [onLinkPress] - Callback when the CTA is pressed.
|
|
261
|
+
*/
|
|
262
|
+
const MessageCardBanner = React.forwardRef<View, MessageCardBannerProps>(
|
|
263
|
+
(
|
|
264
|
+
{
|
|
265
|
+
colourScheme = "primary",
|
|
266
|
+
media,
|
|
267
|
+
title,
|
|
268
|
+
children,
|
|
269
|
+
linkLabel,
|
|
270
|
+
linkHref,
|
|
271
|
+
onLinkPress,
|
|
272
|
+
...rest
|
|
273
|
+
},
|
|
274
|
+
ref
|
|
275
|
+
) => {
|
|
276
|
+
const theme = useTheme()
|
|
277
|
+
const { colour, dimensions } = theme.tokens.semantics
|
|
278
|
+
|
|
279
|
+
const bgColor =
|
|
280
|
+
colourScheme === "secondary"
|
|
281
|
+
? colour.background.container.secondary
|
|
282
|
+
: colour.background.container.alt
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<StyledBannerRoot
|
|
286
|
+
ref={ref}
|
|
287
|
+
rootGap={parseTokenValue(dimensions.spacing.md)}
|
|
288
|
+
rootPadding={parseTokenValue(dimensions.spacing.md)}
|
|
289
|
+
rootBorderRadius={parseTokenValue(dimensions.borderRadius.md)}
|
|
290
|
+
rootBgColor={bgColor}
|
|
291
|
+
accessible
|
|
292
|
+
accessibilityRole="summary"
|
|
293
|
+
{...rest}
|
|
294
|
+
>
|
|
295
|
+
{media?.type === "illustration" ? (
|
|
296
|
+
<StyledBannerIllustrationSlot>
|
|
297
|
+
{React.createElement(media.illustration, {
|
|
298
|
+
width: 108,
|
|
299
|
+
height: 108
|
|
300
|
+
})}
|
|
301
|
+
</StyledBannerIllustrationSlot>
|
|
302
|
+
) : media?.type === "image" ? (
|
|
303
|
+
<StyledBannerImage
|
|
304
|
+
imageRadius={parseTokenValue(dimensions.borderRadius.sm)}
|
|
305
|
+
imageBgColor={colour.background.surface.secondary}
|
|
306
|
+
>
|
|
307
|
+
<Image
|
|
308
|
+
source={media.source}
|
|
309
|
+
accessibilityLabel={media.alt}
|
|
310
|
+
style={{ width: "100%", height: "100%" }}
|
|
311
|
+
resizeMode="cover"
|
|
312
|
+
/>
|
|
313
|
+
</StyledBannerImage>
|
|
314
|
+
) : null}
|
|
315
|
+
<StyledBannerContents
|
|
316
|
+
contentsGap={parseTokenValue(dimensions.spacing.xs)}
|
|
317
|
+
>
|
|
318
|
+
<StyledBannerCopy
|
|
319
|
+
copyGap={parseTokenValue(dimensions.spacing["3xs"])}
|
|
320
|
+
>
|
|
321
|
+
{title && (
|
|
322
|
+
<Typography variant="heading" size="xs" color="primary">
|
|
323
|
+
{title}
|
|
324
|
+
</Typography>
|
|
325
|
+
)}
|
|
326
|
+
<Typography variant="body" size="md" color="secondary">
|
|
327
|
+
{children}
|
|
328
|
+
</Typography>
|
|
329
|
+
</StyledBannerCopy>
|
|
330
|
+
{linkLabel && (linkHref || onLinkPress) && (
|
|
331
|
+
<Link
|
|
332
|
+
href={linkHref}
|
|
333
|
+
onPress={onLinkPress}
|
|
334
|
+
standalone
|
|
335
|
+
size="md"
|
|
336
|
+
weight="semiBold"
|
|
337
|
+
>
|
|
338
|
+
{linkLabel}
|
|
339
|
+
</Link>
|
|
340
|
+
)}
|
|
341
|
+
</StyledBannerContents>
|
|
342
|
+
</StyledBannerRoot>
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
MessageCardBanner.displayName = "MessageCard.Banner"
|
|
348
|
+
|
|
349
|
+
// ─── Public namespace ─────────────────────────────────────────────────────────
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Compound component exposing two sub-components:
|
|
353
|
+
*
|
|
354
|
+
* - `MessageCard.Insight` — yellow trust-signal card
|
|
355
|
+
* - `MessageCard.Banner` — horizontal image/illustration + copy + optional CTA
|
|
356
|
+
*/
|
|
357
|
+
export const MessageCard = {
|
|
358
|
+
Insight: MessageCardInsight,
|
|
359
|
+
Banner: MessageCardBanner
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export { MessageCardInsight, MessageCardBanner }
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { View, StyleSheet } from "react-native"
|
|
3
|
+
import { Notification } from "./Notification"
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: "Molecules/Notification",
|
|
7
|
+
component: Notification,
|
|
8
|
+
argTypes: {
|
|
9
|
+
variant: {
|
|
10
|
+
control: "select",
|
|
11
|
+
options: ["inline", "toast", "system"],
|
|
12
|
+
description: "Layout variant"
|
|
13
|
+
},
|
|
14
|
+
type: {
|
|
15
|
+
control: "select",
|
|
16
|
+
options: ["error", "success", "warning", "info"],
|
|
17
|
+
description: "Severity type controlling colours and icon"
|
|
18
|
+
},
|
|
19
|
+
showIcon: {
|
|
20
|
+
control: "boolean",
|
|
21
|
+
description: "Whether to show the status icon"
|
|
22
|
+
},
|
|
23
|
+
title: {
|
|
24
|
+
control: "text",
|
|
25
|
+
description: "Optional headline (inline variant only)"
|
|
26
|
+
},
|
|
27
|
+
children: {
|
|
28
|
+
control: "text",
|
|
29
|
+
description: "Notification body text"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// --- Playground ---
|
|
35
|
+
|
|
36
|
+
export const Playground = {
|
|
37
|
+
args: {
|
|
38
|
+
variant: "inline",
|
|
39
|
+
type: "error",
|
|
40
|
+
showIcon: true,
|
|
41
|
+
title: "Optional headline here",
|
|
42
|
+
children: "Try to keep the copy length here to a maximum of 2 lines"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// --- Inline / All Types ---
|
|
47
|
+
|
|
48
|
+
export const AllInlineVariants = {
|
|
49
|
+
name: "Inline / All Types",
|
|
50
|
+
render: () => (
|
|
51
|
+
<View style={styles.column}>
|
|
52
|
+
<Notification
|
|
53
|
+
type="error"
|
|
54
|
+
title="Optional headline here"
|
|
55
|
+
onClose={() => {}}
|
|
56
|
+
>
|
|
57
|
+
Try to keep the copy length here to a maximum of 2 lines
|
|
58
|
+
</Notification>
|
|
59
|
+
<Notification
|
|
60
|
+
type="success"
|
|
61
|
+
title="Optional headline here"
|
|
62
|
+
onClose={() => {}}
|
|
63
|
+
>
|
|
64
|
+
Try to keep the copy length here to a maximum of 2 lines
|
|
65
|
+
</Notification>
|
|
66
|
+
<Notification
|
|
67
|
+
type="warning"
|
|
68
|
+
title="Optional headline here"
|
|
69
|
+
onClose={() => {}}
|
|
70
|
+
>
|
|
71
|
+
Try to keep the copy length here to a maximum of 2 lines
|
|
72
|
+
</Notification>
|
|
73
|
+
<Notification
|
|
74
|
+
type="info"
|
|
75
|
+
title="Optional headline here"
|
|
76
|
+
onClose={() => {}}
|
|
77
|
+
>
|
|
78
|
+
Try to keep the copy length here to a maximum of 2 lines
|
|
79
|
+
</Notification>
|
|
80
|
+
</View>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// --- Toast / All Types ---
|
|
85
|
+
|
|
86
|
+
export const AllToastVariants = {
|
|
87
|
+
name: "Toast / All Types",
|
|
88
|
+
render: () => (
|
|
89
|
+
<View style={styles.column}>
|
|
90
|
+
<Notification
|
|
91
|
+
variant="toast"
|
|
92
|
+
type="error"
|
|
93
|
+
onClose={() => {}}
|
|
94
|
+
link={{ label: "Optional link" }}
|
|
95
|
+
>
|
|
96
|
+
Lorem ipsum dolor sit amet consectetur. Massa vestibulum eleifend sed
|
|
97
|
+
phasellus duis odio nunc massa.
|
|
98
|
+
</Notification>
|
|
99
|
+
<Notification
|
|
100
|
+
variant="toast"
|
|
101
|
+
type="success"
|
|
102
|
+
onClose={() => {}}
|
|
103
|
+
link={{ label: "Optional link" }}
|
|
104
|
+
>
|
|
105
|
+
Lorem ipsum dolor sit amet consectetur. Massa vestibulum eleifend sed
|
|
106
|
+
phasellus duis odio nunc massa.
|
|
107
|
+
</Notification>
|
|
108
|
+
<Notification
|
|
109
|
+
variant="toast"
|
|
110
|
+
type="warning"
|
|
111
|
+
onClose={() => {}}
|
|
112
|
+
link={{ label: "Optional link" }}
|
|
113
|
+
>
|
|
114
|
+
Lorem ipsum dolor sit amet consectetur. Massa vestibulum eleifend sed
|
|
115
|
+
phasellus duis odio nunc massa.
|
|
116
|
+
</Notification>
|
|
117
|
+
<Notification
|
|
118
|
+
variant="toast"
|
|
119
|
+
type="info"
|
|
120
|
+
onClose={() => {}}
|
|
121
|
+
link={{ label: "Optional link" }}
|
|
122
|
+
>
|
|
123
|
+
Lorem ipsum dolor sit amet consectetur. Massa vestibulum eleifend sed
|
|
124
|
+
phasellus duis odio nunc massa.
|
|
125
|
+
</Notification>
|
|
126
|
+
</View>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --- System / Small ---
|
|
131
|
+
|
|
132
|
+
export const SystemSmall = {
|
|
133
|
+
name: "System / Small",
|
|
134
|
+
render: () => (
|
|
135
|
+
<View style={styles.column}>
|
|
136
|
+
<Notification variant="system" type="error" size="sm">
|
|
137
|
+
Your order is confirmed and will be with you within two weeks
|
|
138
|
+
</Notification>
|
|
139
|
+
<Notification variant="system" type="success" size="sm">
|
|
140
|
+
Your order is confirmed and will be with you within two weeks
|
|
141
|
+
</Notification>
|
|
142
|
+
<Notification variant="system" type="warning" size="sm">
|
|
143
|
+
Your order is confirmed and will be with you within two weeks
|
|
144
|
+
</Notification>
|
|
145
|
+
<Notification variant="system" type="info" size="sm">
|
|
146
|
+
Your order is confirmed and will be with you within two weeks
|
|
147
|
+
</Notification>
|
|
148
|
+
</View>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// --- System / Large ---
|
|
153
|
+
|
|
154
|
+
export const SystemLarge = {
|
|
155
|
+
name: "System / Large",
|
|
156
|
+
render: () => (
|
|
157
|
+
<View style={styles.column}>
|
|
158
|
+
<Notification variant="system" type="error" size="lg">
|
|
159
|
+
Due to a nationwide issue with DPD, we are experiencing 24-48 hour
|
|
160
|
+
delivery delays on all boxes.
|
|
161
|
+
</Notification>
|
|
162
|
+
<Notification variant="system" type="success" size="lg">
|
|
163
|
+
Due to a nationwide issue with DPD, we are experiencing 24-48 hour
|
|
164
|
+
delivery delays on all boxes.
|
|
165
|
+
</Notification>
|
|
166
|
+
<Notification variant="system" type="warning" size="lg">
|
|
167
|
+
Due to a nationwide issue with DPD, we are experiencing 24-48 hour
|
|
168
|
+
delivery delays on all boxes.
|
|
169
|
+
</Notification>
|
|
170
|
+
<Notification variant="system" type="info" size="lg">
|
|
171
|
+
Due to a nationwide issue with DPD, we are experiencing 24-48 hour
|
|
172
|
+
delivery delays on all boxes.
|
|
173
|
+
</Notification>
|
|
174
|
+
</View>
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// --- States ---
|
|
179
|
+
|
|
180
|
+
export const WithoutIcon = {
|
|
181
|
+
name: "Without Icon",
|
|
182
|
+
render: () => (
|
|
183
|
+
<View style={styles.column}>
|
|
184
|
+
<Notification type="info" showIcon={false} title="No icon">
|
|
185
|
+
This notification has the icon hidden.
|
|
186
|
+
</Notification>
|
|
187
|
+
</View>
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export const WithoutTitle = {
|
|
192
|
+
name: "Inline / Without Title",
|
|
193
|
+
render: () => (
|
|
194
|
+
<View style={styles.column}>
|
|
195
|
+
<Notification type="warning">
|
|
196
|
+
This inline notification has no title, just body text.
|
|
197
|
+
</Notification>
|
|
198
|
+
</View>
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export const ToastWithoutLink = {
|
|
203
|
+
name: "Toast / Without Link",
|
|
204
|
+
render: () => (
|
|
205
|
+
<View style={styles.column}>
|
|
206
|
+
<Notification variant="toast" type="success" onClose={() => {}}>
|
|
207
|
+
A simple toast notification without a link.
|
|
208
|
+
</Notification>
|
|
209
|
+
</View>
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const styles = StyleSheet.create({
|
|
214
|
+
column: {
|
|
215
|
+
flexDirection: "column",
|
|
216
|
+
gap: 16,
|
|
217
|
+
padding: 16
|
|
218
|
+
}
|
|
219
|
+
})
|