@butternutbox/pawprint-native 0.1.0 → 0.2.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.
@@ -1,8 +1,8 @@
1
1
  import React from "react"
2
2
  import { View, Pressable, ViewProps, PressableProps } from "react-native"
3
+ import Svg, { Path } from "react-native-svg"
3
4
  import styled from "@emotion/native"
4
5
  import { useTheme } from "@emotion/react"
5
- import { Icon, type PawprintIcon } from "../../atoms/Icon"
6
6
  import { Illustration } from "../../atoms/Illustration"
7
7
  import { Typography } from "../../atoms/Typography"
8
8
  import { MessageCard } from "../MessageCard"
@@ -17,7 +17,6 @@ type PawprintIllustration = React.ComponentType<{
17
17
  export type PictureSelectorItem = {
18
18
  value: string
19
19
  illustration?: PawprintIllustration
20
- icon?: PawprintIcon
21
20
  disabled?: boolean
22
21
  ariaLabel?: string
23
22
  insightTitle?: string
@@ -48,11 +47,44 @@ type PressableOwnForItem = Omit<PressableProps, "onPress" | "disabled">
48
47
  // ─── Pointer shape ────────────────────────────────────────────────────────────
49
48
 
50
49
  // TODO: add to pictureSelector.json — pointer is 24w × 12h triangle in Figma.
51
- // These aren't container dimensions — they're the triangle's border widths
52
- // (the CSS/RN triangle trick), which *are* the shape itself.
53
- const POINTER_HALF_WIDTH = 12
50
+ const POINTER_WIDTH = 24
54
51
  const POINTER_HEIGHT = 12
55
52
 
53
+ // Build an SVG path for the rounded-corner pointer: apex centered at the top
54
+ // of a 24×12 box, bottom edge along the bottom. `r` is the corner radius. The
55
+ // triangle is isoceles with apex interior angle 90° and bottom interior angles
56
+ // 45°, so the inset distances along each edge from a vertex are r·cot(θ/2) —
57
+ // i.e. r at the apex and r·(√2 + 1) at the bottom corners.
58
+ const buildPointerPath = (r: number): string => {
59
+ const w = POINTER_WIDTH
60
+ const h = POINTER_HEIGHT
61
+ const apexInset = r
62
+ const botInset = r * (Math.SQRT2 + 1)
63
+ const sin45 = Math.SQRT1_2
64
+ const apexAxis = apexInset * sin45
65
+ const botAxis = botInset * sin45
66
+
67
+ const apexLeftX = w / 2 - apexAxis
68
+ const apexRightX = w / 2 + apexAxis
69
+ const apexY = apexAxis
70
+ const blEdgeX = botAxis
71
+ const blEdgeY = h - botAxis
72
+ const blBottomX = botInset
73
+ const brBottomX = w - botInset
74
+ const brEdgeX = w - botAxis
75
+
76
+ return [
77
+ `M ${blEdgeX} ${blEdgeY}`,
78
+ `L ${apexLeftX} ${apexY}`,
79
+ `A ${r} ${r} 0 0 1 ${apexRightX} ${apexY}`,
80
+ `L ${brEdgeX} ${blEdgeY}`,
81
+ `A ${r} ${r} 0 0 1 ${brBottomX} ${h}`,
82
+ `L ${blBottomX} ${h}`,
83
+ `A ${r} ${r} 0 0 1 ${blEdgeX} ${blEdgeY}`,
84
+ "Z"
85
+ ].join(" ")
86
+ }
87
+
56
88
  // ─── Helpers ──────────────────────────────────────────────────────────────────
57
89
 
58
90
  const parseTokenValue = (value: string): number => parseFloat(value)
@@ -120,30 +152,13 @@ const StyledPictureButton = styled(Pressable)<{
120
152
  })
121
153
  )
122
154
 
123
- const StyledPointerTriangle = styled(View)<{
124
- pointerColor: string
125
- pointerVisible: boolean
126
- }>(({ pointerColor, pointerVisible }) => ({
127
- // 0×0 is part of the RN triangle-via-borders trick, not a layout
128
- // dimension. Borders compose into a down-pointing triangle (apex at
129
- // bottom) on RN; the `rotate 180deg` transform flips it to apex-up so
130
- // the pointer visually connects the selected button to the insight
131
- // card below. Using rotate instead of `borderBottom` because RN's
132
- // triangle-via-borders rendering is inconsistent when the coloured
133
- // border is on the bottom with transparent left/right on certain
134
- // platforms.
135
- width: 0,
136
- height: 0,
137
- borderLeftWidth: POINTER_HALF_WIDTH,
138
- borderRightWidth: POINTER_HALF_WIDTH,
139
- borderTopWidth: POINTER_HEIGHT,
140
- borderLeftColor: "transparent",
141
- borderRightColor: "transparent",
142
- borderTopColor: pointerColor,
143
- borderStyle: "solid",
144
- transform: [{ rotate: "180deg" }],
145
- opacity: pointerVisible ? 1 : 0
146
- }))
155
+ const StyledPointer = styled(Svg)<{ pointerVisible: boolean }>(
156
+ ({ pointerVisible }) => ({
157
+ width: POINTER_WIDTH,
158
+ height: POINTER_HEIGHT,
159
+ opacity: pointerVisible ? 1 : 0
160
+ })
161
+ )
147
162
 
148
163
  // ─── Component ────────────────────────────────────────────────────────────────
149
164
 
@@ -217,6 +232,9 @@ export const PictureSelector = React.forwardRef<View, PictureSelectorProps>(
217
232
  pictureButton.borderWidth.default
218
233
  )
219
234
  const borderWidthActive = parseTokenValue(pictureButton.borderWidth.active)
235
+ const pointerPath = buildPointerPath(
236
+ parseTokenValue(pictureButton.pointer.borderRadius.default)
237
+ )
220
238
  const resolvedMinWidth =
221
239
  itemMinWidth ?? parseTokenValue(pictureButton.size.minWidth)
222
240
  // Default min-height to min-width so the cards stay square at the floor,
@@ -244,7 +262,6 @@ export const PictureSelector = React.forwardRef<View, PictureSelectorProps>(
244
262
  {items.map((item) => {
245
263
  const isSelected = item.value === selectedValue
246
264
  const Visual = item.illustration
247
- const IconComp = item.icon
248
265
  const buttonProps: Omit<
249
266
  React.ComponentProps<typeof StyledPictureButton>,
250
267
  keyof PressableOwnForItem
@@ -284,15 +301,20 @@ export const PictureSelector = React.forwardRef<View, PictureSelectorProps>(
284
301
  {...buttonProps}
285
302
  >
286
303
  {Visual ? (
287
- <Illustration illustration={Visual} size="sm" />
288
- ) : IconComp ? (
289
- <Icon icon={IconComp} size="2xl" />
304
+ <Illustration illustration={Visual} size="xl" />
290
305
  ) : null}
291
306
  </StyledPictureButton>
292
- <StyledPointerTriangle
293
- pointerColor={pictureButton.colour.pointer.default}
307
+ <StyledPointer
294
308
  pointerVisible={isSelected && hasInsight}
295
- />
309
+ width={POINTER_WIDTH}
310
+ height={POINTER_HEIGHT}
311
+ viewBox={`0 0 ${POINTER_WIDTH} ${POINTER_HEIGHT}`}
312
+ >
313
+ <Path
314
+ d={pointerPath}
315
+ fill={pictureButton.colour.pointer.default}
316
+ />
317
+ </StyledPointer>
296
318
  </StyledItemStack>
297
319
  )
298
320
  })}